<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Dillon Codes]]></title><description><![CDATA[I'm Dillon and I like to code. I code for work, I code for fun, I read about code and I think about code. I share some of that code here and I do my best to sha]]></description><link>https://dilloncodes.com</link><generator>RSS for Node</generator><lastBuildDate>Sun, 12 Apr 2026 03:21:29 GMT</lastBuildDate><atom:link href="https://dilloncodes.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Completion Handlers in Swift]]></title><description><![CDATA[Completion handlers are everywhere in Swift. They are commonly used in Apple's frameworks and many third-party libraries. They are often put forward as an alternative to delegation and the target action pattern, especially in the context of long-runn...]]></description><link>https://dilloncodes.com/completion-handlers-in-swift</link><guid isPermaLink="true">https://dilloncodes.com/completion-handlers-in-swift</guid><category><![CDATA[iOS]]></category><category><![CDATA[Swift]]></category><category><![CDATA[ios app development]]></category><dc:creator><![CDATA[Dillon McElhinney]]></dc:creator><pubDate>Sat, 22 Oct 2022 21:00:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1666472339550/zDm0mwe6F.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Completion handlers are everywhere in Swift. They are commonly used in Apple's frameworks and many third-party libraries. They are often put forward as an alternative to <a target="_blank" href="https://dilloncodes.com/delegate-pattern-in-swift">delegation</a> and the target action pattern, especially in the context of long-running work like fetching data over the network or processing images. You might see them referred to as "handlers" or "completion blocks", simply "completions", or maybe something else depending on the conventions of the code base you are working in.</p>
<p>Today we are going to look at what the completion handler pattern is and the main problem it solves. We'll look at how completion handlers are actually implemented in Swift. And we'll look at some downsides to the pattern and some examples of how to work around them.</p>
<h2 id="heading-what-is-a-completion-handler">What Is A Completion Handler?</h2>
<p>At the most basic level, <strong>a completion handler is a block of work that is passed as an argument to a function so that it can run at a later time</strong>. There are many reasons why we might want to use them, but they are especially powerful when we have a task that will take a long time to finish and we don't want that task to block other work. That's a pretty abstract sentence, so let's think through an example.</p>
<p>Imagine a user taps a button in your app that is supposed to load some data. Let's say it is a list of pets who need to be adopted in the zip code the user entered. (It doesn't matter what the data is, but why not imagine cute animals?) The button tap will be handled on the main thread (which is used for all UI updates on iOS) and that will kick off a network request to load the data, and when that returns it will update the UI to show the list of adorable pets. The simplest way to code this would be to just kick off the network request and then sit and wait for it to return and then update the UI when it does.</p>
<p>But what if that request took 2 seconds? or 5 seconds? The user wouldn't be able to do anything while they waited. They couldn't navigate around the rest of the app, they couldn't scroll or tap any other buttons or anything else, because that is all code that has to run and we're already waiting for the network request to return. We're <strong>blocking</strong> other work. That is a terrible user experience.</p>
<p>We have very little control over how long that network request will take, but we can improve the UX here. Instead of waiting, we'll put the work that takes a long time (in this case the network request) on another thread that won't block our UI thread. (Pretty much all of the abstractions that are used for network requests on iOS do exactly that for us under the hood.) That solves the problem of blocking the user from being able to use the app. But what about when the data comes back? How do we get that data to show up in our UI?</p>
<p>That is where completion handlers come in. When we kick off the network request in the first place, we'll give it a completion handler – block of work to run once the request returns. We'll trigger our UI update in that block of work.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666461859226/l111zeDet.png" alt="single-multi-thread.png" /></p>
<p>Now that we have a high-level idea of what a completion handler is and what problem they solve, let's dig into what they actually look like in Swift. To do that, we need to look at the concept of closures as a whole and a little bit about their semantics. </p>
<h2 id="heading-a-brief-diversion-on-closures">A Brief Diversion On Closures</h2>
<p>As the <a target="_blank" href="https://docs.swift.org/swift-book/LanguageGuide/Closures.html">Swift Language Guide</a> says:</p>
<blockquote>
<p><em>Closures</em> are self-contained blocks of functionality that can be passed around and used in your code.</p>
</blockquote>
<p>In other languages you might see them referred to as "blocks" or "lambdas", but in Swift the name comes from their ability to capture or "close over" constants and variables in the context where they are defined. We'll touch on that more in a minute.</p>
<p>So a closure is a block of code that can take arguments and returns some value. Sounds a lot like a function, right? That's because in Swift functions are actually a specialized type of closure. They have a name and some special syntax, but you can pass a function anywhere you can pass a closure. Just as long as the signature matches. </p>
<p>That means a function which takes a completion handler is actually a closure that takes another closure to run at some later point. It's closures all the way down! This is usually where I see people's eyes start to glaze over, but it is worth understanding if you can stick with it.</p>
<p>In Swift these are all closures, and they would all do the same thing:</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">firstAdd</span><span class="hljs-params">(<span class="hljs-number">_</span> a: Int, <span class="hljs-number">_</span> b: Int)</span></span> -&gt; <span class="hljs-type">Int</span> { <span class="hljs-keyword">return</span> a + b }

<span class="hljs-keyword">let</span> secondAdd: (<span class="hljs-type">Int</span>, <span class="hljs-type">Int</span>) -&gt; <span class="hljs-type">Int</span> = { a, b <span class="hljs-keyword">in</span> <span class="hljs-keyword">return</span> a + b }

<span class="hljs-keyword">let</span> thirdAdd = { (a: <span class="hljs-type">Int</span>, b: <span class="hljs-type">Int</span>) -&gt; <span class="hljs-type">Int</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">return</span> a + b }

<span class="hljs-keyword">let</span> fourthAdd: (<span class="hljs-type">Int</span>, <span class="hljs-type">Int</span>) -&gt; <span class="hljs-type">Int</span> = { $<span class="hljs-number">0</span> + $<span class="hljs-number">1</span> }
</code></pre>
<p>The first one is a normal function that you are probably used to seeing. The second defines the signature of the closure and then the closure itself, giving names ("a" and "b") to the arguments. The third defines the signature and names together within the closure. The fourth uses the anonymous argument labels ("$0" meaning the first argument, "$1" meaning the second, and so on) and omits the <code>return</code> keyword, which is allowed because this is a one line closure and the return value can be inferred as the result of that line. </p>
<p>If you wanted to add two values you could call any of these in the same way:</p>
<pre><code class="lang-swift">firstAdd(<span class="hljs-number">2</span>, <span class="hljs-number">2</span>)
secondAdd(<span class="hljs-number">2</span>, <span class="hljs-number">2</span>)
thirdAdd(<span class="hljs-number">2</span>, <span class="hljs-number">2</span>)
fourthAdd(<span class="hljs-number">2</span>, <span class="hljs-number">2</span>)
</code></pre>
<h3 id="heading-capturing-values">Capturing Values</h3>
<p>This is great if you have the values you want to pass to the closure and the logic is trivial like it is here. But what if we want to keep track of some state beyond an individual run of the closure? That is where <em>capturing</em> variables comes in. Let's say we have a game and we're keeping track of a score for a given user. We might model our logic like this:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// simple object to encapsulate different difficulties in our game</span>
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Difficulty</span> </span>{
    <span class="hljs-keyword">let</span> pointValue: <span class="hljs-type">Int</span>

    <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> easy = <span class="hljs-type">Difficulty</span>(pointValue: <span class="hljs-number">10</span>)
    <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> hard = <span class="hljs-type">Difficulty</span>(pointValue: <span class="hljs-number">6</span>)
}

<span class="hljs-comment">// global function for generating a new counter of a given difficulty</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">scoreCounter</span><span class="hljs-params">(diffculty: Difficulty = .easy)</span></span> -&gt; () -&gt; <span class="hljs-type">Int</span> {
    <span class="hljs-keyword">var</span> score: <span class="hljs-type">Int</span> = <span class="hljs-number">0</span>

    <span class="hljs-keyword">let</span> adder: () -&gt; <span class="hljs-type">Int</span> = {
        score += diffculty.pointValue
        <span class="hljs-keyword">return</span> score
    }
    <span class="hljs-keyword">return</span> adder
}

<span class="hljs-keyword">let</span> easyCounter = scoreCounter()
<span class="hljs-built_in">print</span>(easyCounter()) <span class="hljs-comment">// 10</span>
<span class="hljs-built_in">print</span>(easyCounter()) <span class="hljs-comment">// 20</span>

<span class="hljs-keyword">let</span> hardCounter = scoreCounter(diffculty: .hard)
<span class="hljs-built_in">print</span>(hardCounter()) <span class="hljs-comment">// 6</span>
<span class="hljs-built_in">print</span>(hardCounter()) <span class="hljs-comment">// 12</span>
</code></pre>
<p>Look at the <code>scoreCounter</code> function. We are going to give it a <code>Difficulty</code> and it is going to give us a closure which takes no value and returns an <code>Int</code>. Internally, it declares a variable called <code>score</code> and sets it to <code>0</code>. Then it makes a closure called <code>adder</code> which itself adds the difficulty's <code>pointValue</code> to the <code>score</code> and then returns the new score. Finally, it returns the <code>adder</code>. </p>
<p>The magic here is that the closure "captures" a reference to the <code>score</code> variable and also to the difficulty's <code>pointValue</code>. So it will actually increase each time we call the closure, even though we're not holding onto that <code>score</code> variable anywhere. And each instance of the counter will have its own <code>score</code> variable.</p>
<h3 id="heading-escaping">Escaping</h3>
<p>This may not seem like much, and in all honesty, it is probably not the best way to model a score counter, but when we combine it with the next feature of closures we start to see where it all comes together. Closures can also "escape" the scope where they are passed. </p>
<p>That means they can outlive the scope of where they are passed. This is a fairly confusing concept, but it basically boils down to a function (A) saying that the closure it accepts (B) may run after the function has returned (C). In our score counter example it might look like this:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// A.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">scoreCounter</span><span class="hljs-params">(modify: @escaping <span class="hljs-params">(Int)</span></span></span> -&gt; <span class="hljs-type">Int</span>) -&gt; (<span class="hljs-type">Int</span>) -&gt; <span class="hljs-type">Int</span> {
    <span class="hljs-keyword">var</span> score: <span class="hljs-type">Int</span> = <span class="hljs-number">0</span>

    <span class="hljs-keyword">let</span> adder: (<span class="hljs-type">Int</span>) -&gt; <span class="hljs-type">Int</span> = {
        score += modify($<span class="hljs-number">0</span>) <span class="hljs-comment">// C.</span>
        <span class="hljs-keyword">return</span> score
    }
    <span class="hljs-keyword">return</span> adder
}

<span class="hljs-keyword">let</span> modify: (<span class="hljs-type">Int</span>) -&gt; <span class="hljs-type">Int</span> = { $<span class="hljs-number">0</span> * <span class="hljs-number">8</span> / <span class="hljs-number">10</span> } <span class="hljs-comment">// B.</span>
<span class="hljs-keyword">let</span> customCounter = scoreCounter(modify: modify)
<span class="hljs-built_in">print</span>(customCounter(<span class="hljs-number">10</span>)) <span class="hljs-comment">// 8</span>
<span class="hljs-built_in">print</span>(customCounter(<span class="hljs-number">10</span>)) <span class="hljs-comment">// 16</span>
</code></pre>
<p>The <code>scoreCounter</code> function takes a <code>modify</code> closure and it is going to build an <code>adder</code> closure to return. Since the <code>modify</code> closure it takes as an argument is going to be called by the <code>adder</code> closure it returns, it may be called after <code>scoreCounter</code> has finished its work, so we have to mark it as <code>@escaping</code>.</p>
<h2 id="heading-back-to-completion-handlers">Back To Completion Handlers</h2>
<p>So for our example we might write something like this, with a completion handler:</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fetchPets</span><span class="hljs-params">(then completion: @escaping <span class="hljs-params">([Pet])</span></span></span> -&gt; <span class="hljs-type">Void</span>) { ... }
</code></pre>
<p>The caller of the function will pass a block of work that it wants to do with the fetched array of pets. Then it can continue with whatever work comes next without being blocked by the the work that needs to be done to fetch the pets. The "fetching pets work" will happen on a background thread and when it is done the "completion handler" work that the caller defined will run.</p>
<p>The the call site would look something like this:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// in PetListViewController</span>
fetchPets { pets <span class="hljs-keyword">in</span>
    <span class="hljs-keyword">self</span>.updateUI(with: pets)
}
</code></pre>
<p>The pet list view controller says "I want to you to fetch the list of pets, and when you're done I want you to call my update UI function with the pets that you fetched".</p>
<p><em>I don't know about you, but I find that it is a lot easier to follow the logic if I personify the different classes and objects in my code and imagine them having conversations with each other.</em></p>
<p>Now we've made it all the way through the diagram! We sent our long running work to a background thread where it doesn't block the UI and we've used a completion handler to update our UI when it is finished. You can see that the completion handler is marked as <code>@escaping</code>, because it will be called after the <code>fetchPets</code> function has returned. And it is easy to miss, but we are actually capturing a reference to <code>self</code> here. The closure will hold onto a reference to the pet list view controller (which is what <code>self</code> refers to in this case) as long as it (the closure) remains in memory.</p>
<p>It seems like we've solved our problem. That's great, but what can go wrong? Let's look at some things you should watch out for as you use completion handlers in your code.</p>
<h2 id="heading-some-pitfalls">Some Pitfalls</h2>
<h3 id="heading-ui-work-on-a-background-thread">UI Work On A Background Thread</h3>
<p>One of the most common mistakes I see beginners make with completion handlers is updating their UI on the background thread a network response it returned on. This usually stems from the the fact that you almost never have to dispatch your network request to a background thread in the first place (it is done for you) and so many people don't realize their completion handler will even be called on a background thread. Plus it is easy to forget and it usually won't lead to outright crashes, just odd behavior.</p>
<p>Fortunately it is relatively easy to fix. We just need to make sure the UI work runs on the UI queue. We can do that using this function called <code>DispatchQueue.async(group:qos:flags:execute:)</code>, not to be confused with the <code>async</code> keyword introduced in Swift 5.5. This function takes a closure and runs it on the queue it is called on asynchronously (there is also a synchronous version called <code>sync</code>), with some optional configuration. In our example it would look like this:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// in PetListViewController</span>
fetchPets { pets <span class="hljs-keyword">in</span>
    <span class="hljs-type">DispatchQueue</span>.main.async { <span class="hljs-keyword">self</span>.updateUI(with: pets) }
}
</code></pre>
<p>Another tool that helps here is the "Main Thread Checker", which you can enable in the Scheme Editor, on the "Run" tab, under "Diagnostics". This will cause your app to pause execution when your app is running and you try to update the UI from any thread other than the main one. It will even try to show you where you're doing it, although I find in real usage that it isn't always 100% accurate.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666461881501/-fROwiABi.png" alt="main-thread-checker.png" /></p>
<h3 id="heading-retention-cycles">Retention Cycles</h3>
<p>Another common mistake that people make with completion handlers is causing a retain cycle. To really get to the bottom of what that means and why it can be a problem we'd have to dive deep into how Swift handles memory management. I'm not going to do that in this article, but you can <a target="_blank" href="https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html">read more about it here</a> if you're interested. For now, let's just say that a retain cycle is when two or more objects each have a strong reference (one which adds to the retain count) to one another so that there is a closed loop of references which will hold those objects in memory indefinitely. And let's say that is generally a bad thing that we want to avoid.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666461892935/Jn8ndUEJ1.png" alt="strong-reference-cycle.png" /></p>
<p>We can avoid that cycle if we break one of the references. In Swift we can do that with either the <code>weak</code> or <code>unowned</code> keywords. Both of these keywords will keep a reference to the object without adding to its retain count.  <code>weak</code> references must be <code>Optional</code>, to acknowledge it may not be there in the future when you try to access it. <code>unowned</code> is similar to an implictly unwrapped optional, in that you are telling the compiler to treat the reference as always being there even though it is possible for it to not be. There are times when <code>unowned</code> is the right method to use, but generally speaking it is safer to use <code>weak</code> and that is probably what you should do unless you have a good reason to do otherwise.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666461905317/0As7YHN8M.png" alt="weak-reference-cycle.png" /></p>
<p><em>Some examples of when to use <code>unowned</code> would be if the captured reference will never become <code>nil</code>, or when the closure and the instance it captures will always refer to each other and will always be deallocated at the same time. In either of these cases though, you could use <code>weak</code> and it will still resolve the retain cycle it just may be slightly less performant.</em></p>
<p>In a closure you can make a reference <code>weak</code> or <code>unowned</code> with the "capture list". So far when we've captured references in a closure it has been implicit – the compiler just handles it for us if we use a reference in the closure. But we can also explicitly capture references using the capture list. You do that by putting them in square brackets before the argument definition in the closure. That looks like this:</p>
<pre><code class="lang-swift">fetchPets { [<span class="hljs-keyword">weak</span> <span class="hljs-keyword">self</span>] pets <span class="hljs-keyword">in</span>
    <span class="hljs-type">DispatchQueue</span>.main.async { <span class="hljs-keyword">self</span>?.updateUI(with: pets) }
}
</code></pre>
<p>So now we are telling the compiler to keep a <code>weak</code> reference to <code>self</code>. That means <code>self</code> now has a type of <code>PetListViewController?</code> and so we use optional chaining on it before calling <code>updateUI</code>. If <code>self</code> is no longer there this block will just return. That safely avoids the retain cycle.</p>
<p><em>You might notice that this is the same method we use to avoid a retain cycle in the delegate pattern, it just looks a little different here because we're using closure semantics. The main difference is that in the delegate pattern, the <code>weak</code> reference is handled as a part of the implementation and in the completion handler pattern it is handled at the call site. This is one reason why I generally prefer the delegate pattern. In that pattern you avoid retain cycles in one easy to check (and lint) place and users of your code don't have to worry about it at the call site. With the completion handler pattern you give that power to the users of your method, which may be more versatile, but also means that they have to do some extra work to avoid retain cycles.</em></p>
<h2 id="heading-wrap-up">Wrap Up</h2>
<p>We've looked at how the completion handler pattern is just giving some other code a block of work to do later. We've looked at how this pattern solves the problem of not wanting to block important work in the short term with something that will take a long time. We've looked at the semantics and how to implement completion handlers in Swift. They may look a little funky at first, especially with some of the syntactic sugar, but they just make use of a combination of Swift features that we use elsewhere. And we've looked at the two main problems people run into with completion handlers – updating the UI from a background thread and inadvertently causing retain cycles – and how to avoid those problems.</p>
]]></content:encoded></item><item><title><![CDATA[My Journey Into App Development]]></title><description><![CDATA[The Kindling
My journey to start developing software as my livelihood took a while. Looking back, I realize that I was always sort of around computers. I grew up in the age when regular people started using the internet. I had to take a few typing an...]]></description><link>https://dilloncodes.com/my-journey-into-app-development</link><guid isPermaLink="true">https://dilloncodes.com/my-journey-into-app-development</guid><category><![CDATA[iOS]]></category><category><![CDATA[development]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Dillon McElhinney]]></dc:creator><pubDate>Sat, 03 Sep 2022 16:48:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/npxXWgQ33ZQ/upload/v1662223612399/ZtdwM86jz.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-the-kindling">The Kindling</h2>
<p>My journey to start developing software as my livelihood took a while. Looking back, I realize that I was always sort of around computers. I grew up in the age when regular people started using the internet. I had to take a few typing and computer classes in school as a kid. I had a few classes that touched on web development with various tools. My dad also did some work with computers and we always had at least one desktop around the house, and often more than one. I even helped him take a few apart and make minor repairs and upgrades over the years.</p>
<p>But it never clicked for me. Code seemed esoteric and boring and I never had a clear picture of what the possibilities were. I played the emulators that my dad would download and set up for me. I used Facebook and Myspace all the time (yes, I was in middle school when Myspace launched). I loved my iPod and the ability it gave me to take all my music wherever I wanted to go. But I never connected the dots that I could be a part of making things like that in the world.</p>
<p>It wasn't until much later that I would catch the bug. After I had earned a Bachelor's degree that I had no real intention of using. After I had spent years learning and working with audio, video and lighting for live production environments (think concerts, conferences, and churches). After I had begun to pay for rent and insurance and taxes, and get a taste of what life is like as an adult.</p>
<h2 id="heading-the-spark">The Spark</h2>
<p>In my first job out of school I was the grunt worker on a team of five very capable production managers at a church of about 5,000 members. As a team we were responsible for maintaining and operating any and all equipment related to audio, video or lighting used in weekend services and auxiliary events. My main role was scheduling the twenty or so volunteers that we needed to make things run every weekend, and I would run sound or lights or direct video for various events and services. And, as the junior member of the team, I would do pretty much whatever else needed to be done.</p>
<p>A few months into the job I was given a task. We used <a target="_blank" href="https://basecamp.com">Basecamp</a> as our project management tool. Everything we needed to get done as a team was kept track of in there. But there was a problem. Occasionally there were events, scheduled by other departments in the church, that didn't make it into our system. On more than one occasion, no one told us that they needed our support for an event until the day of that event! I don't know if you have ever been unexpectedly asked to stay at work for an additional six hours. If you have, it probably wasn't to manage a microphone and music in a room full of two hundred teenagers. But I'm sure you can imagine that those were not the best days.</p>
<p>Now when people would schedule an event they would fill out a form in another piece of software and there were even some questions on that form that asked about what AVL equipment/support they would need. But it was their responsibility to make sure we got that form if we needed it, which often meant we didn't get it. So I was asked to find a way to bridge the gap. To find some automated way of connecting this other piece of software over there to our Basecamp over here.</p>
<p>I dove in on finding a solution. I started learning about a few programming languages. I learned some python and some ruby. We had access to a website called Lynda.com which had educational videos on a variety of topics, including some basic computer science and language material. I watched some of those every day and learned all sorts of things. I wrote some scripts that moved files and folders around on the computer and I felt like a god. I worked and worked on a terrible little script that would solve this problem at work, and I finally accomplished it.</p>
<p>After a few weeks of focused learning and a lot of trial and error I had a script I could run that would copy events over from the church's software to Basecamp. It was very poorly organized. I'm sure it was full of bugs and security vulnerabilities. I would be embarrassed to show it to anyone today (fortunately, I don't have it so I can't show it to anyone even if I wanted to 😁).</p>
<p>My plan was to set it up as a cron job on one of the desktops that lived at the church and have it run every day or so. Since people filled out this form weeks before their event, that would get the information into our system with plenty of time for us to plan for it. But as it happened, we discovered a new web service for connecting various tools called <a target="_blank" href="https://zapier.com">Zapier</a> just as I finished up my script. This was obviously a better solution with support from actual developers and it could serve as the glue between a few other services we used as well. So my script got trashed and we went with Zapier instead.</p>
<p>But this experience was the spark that lit the coding fire in me. I got a taste of a language that felt like magic. I could type some characters into a computer and it would do whatever I told it to. And some of the things I could tell it to do would solve actual problems that I had in my own life or that I knew other people had. I had found a new passion.</p>
<p><em>My advice for myself (or anyone else) in this stage would be to stay open. Be willing to say yes and try new things. You never know what mundane and trivial project you take at work might actually be the start of a whole new career for you.</em></p>
<h2 id="heading-smouldering">Smouldering</h2>
<p>I kept learning, going through courses on Lynda. I stumbled upon Stanford's computer science courses. (They put all the material online and you have access to it all for free, so no excuses if you're trying to get started.) I went through <a target="_blank" href="https://cs193p.sites.stanford.edu">CS 193</a> which was called "Developing Apps for iOS" and I got my first few apps running in a simulator. They were ugly and not very functional and it was the Swift 2 days so the code itself was pretty ugly too. But I had written a piece of software that had a UI, and it could run on the phone in my pocket all on its own. I could hardly believe it.</p>
<p>It would be another four years before I decided to switch fully into software development. I kept dabbling in things over time. I went through a bunch of <a target="_blank" href="https://www.freecodecamp.org">Free Code Camp's material</a> and I read some books and blogs and watched videos on YouTube, etc. But I never felt like I was qualified to get a job.</p>
<p>Throughout this time I learned a lot, and I do not think it was totally wasted time. I established my underlying understanding of how computers work and how programming languages generally work and the sort of logic that programmers use. I slowly saturated myself in the tech world and familiarized myself with the jargon, etc. I still draw on a lot of this base knowledge today.</p>
<p>But I didn't make much actual progress because I didn't take action, or at least I didn't commit to the actions that I took. I built toy apps and websites. I made a "portfolio" website that I never deployed. I never learned Git. I had no stake in the game. It was fun to learn and make pomodoro timers, but if nothing came of it I didn't lose anything but a little time.</p>
<p><em>My advice for myself (or anyone else) in this stage would be to find a way to get a stake in the game. There is infinite material out there to learn and more tutorial projects than you will ever be able to complete. Once you have a hang of the basics, find a way to give yourself something to gain (or lose). You'll be amazed at how much faster you progress. Buy a course. Pay for a bootcamp. Get a contract for a project that is reasonably out of your comfort zone. Make a bet with your friend. Post about your goal on your social media platform of choice. Do something that will have a real cost if you don't accomplish it. Just make sure it is a cost you can recover from.</em></p>
<h2 id="heading-fanning-the-flames">Fanning the Flames</h2>
<p>In the fall of 2018 I decided to quit my job and go through a pretty intense bootcamp that (at the time) was called <a target="_blank" href="https://www.bloomtech.com">Lambda School</a>. The internet seems to have pretty mixed reviews on them, but my experience was exactly what I needed. They had enough structure and accountability to keep me going (it was fully remote, so I was spending all day every day alone in front of a computer). And they built in enough space that I could try things out and learn how to find answers for myself (which is a huge part of the actual job of software development). The iOS cohorts were also pretty small and I met some incredible people along the way.</p>
<p>I got my first job right as I was finishing up my time at Lambda. I had applied for over a hundred, interviewed for a couple, and got an offer for one. It had taken almost a year since I quit my old job, a lot of work, a lot of learning and reading and coding. But I got there. I found someone willing to pay me to write code for an iOS app, and my starting salary was 40% higher than any other job I had ever had in my life.</p>
<p>But that was merely the start. I still had so much to learn. I had to learn about how to work in the context of a team. I had to learn about Agile practices and what "scrum" and "sprint" and "epic" and "story" meant.  I had to learn about the lifecycle of a feature in a company. How it goes from idea to plan to design to implementation to testing to release to maintenance. I had to learn about the push and pull from various parts of the organization from leadership to marketing and sales to design to engineering and how to balance the tension. I had to learn how to communicate my ideas to people who had no context or understanding of the technical constraints and to people who would be implementing the idea themselves. I had to learn how to document decisions and write tests and give demos. I had to learn how to debug CI/CD and network requests. Etc.</p>
<p>In short there was a whole other world of things to learn that I had no idea about before I got an actual job. Tech companies move very fast and structure their work in a very different way from anywhere else I had ever worked. It was a lot to digest while also trying to put out a decent quality and quantity of code as a junior developer. But those lessons and experience are what took me from being a junior developer to being a senior developer.</p>
<p><em>My advice for myself in this stage would be to view yourself as a student. Stay curious. Ask questions. Even (especially) if you feel like they make you look dumb. Listen to the answers and take them to heart. Take the advice that is given to you by the engineers around you. It may come across as a rude comment in a PR review but it is intended to make the code (and you) better. View bugs as opportunities to learn something. View features as opportunities to learn something. View code reviews (both given and received) as opportunities to learn something. Especially as you begin to gain seniority and get paid more it gets easier and easier to think that you have all the answers. But you don't. You'll do yourself a disservice by acting like you do (not to mention those around you, and your users, and your stakeholders).</em></p>
<h2 id="heading-the-fire-rages">The Fire Rages</h2>
<p>Lately I feel I have hit my stride. I have a good rhythm of work and play and learning and rest. I have a job that I enjoy, working on a product that I know improves a lot of people's lives, with people I respect and constantly learn from. I am spending more time mentoring people who a newer to this than I am, doing what I can to help people like innumerable people helped me. I am spending more time sharing what I learn in so others can (hopefully) benefit from it. I am trying to find the balance between having contentment and striving to grow and be better. I am where I wanted to be four years ago.</p>
<p><em>I don't know that I have advice for myself in this stage because I am still in the midst of it. But maybe you do. What advice would you have for someone in this stage of their development journey? Let me know and I'll update this post with any of the good ones.</em></p>
]]></content:encoded></item><item><title><![CDATA[Xcode Tip: Pre-Build Actions]]></title><description><![CDATA[I ran into this cool little piece of functionality in Xcode the other day that helped me solve a real problem. I have never seen anyone mention that you can do this, so I'm sharing it here in the hopes that it will help someone else out.
I was recent...]]></description><link>https://dilloncodes.com/xcode-tip-pre-build-actions</link><guid isPermaLink="true">https://dilloncodes.com/xcode-tip-pre-build-actions</guid><category><![CDATA[iOS]]></category><category><![CDATA[Xcode]]></category><category><![CDATA[tips]]></category><dc:creator><![CDATA[Dillon McElhinney]]></dc:creator><pubDate>Mon, 29 Aug 2022 23:08:48 GMT</pubDate><content:encoded><![CDATA[<p>I ran into this cool little piece of functionality in Xcode the other day that helped me solve a real problem. I have never seen anyone mention that you can do this, so I'm sharing it here in the hopes that it will help someone else out.</p>
<p>I was recently working with a framework which uses a configuration file. This file is stored in JSON and is bundled with the app. I wanted an easy way to have separate configuration files for the <code>dev</code> version of my app and the <code>prod</code> version and I already had separate schemes for these different versions. I was looking into ways to accomplish this, maybe via an argument passed on launch or a compiler flag or something. I was starting to go down the rabbit hole of the various code generation tools etc. But then I noticed something in the scheme editor.</p>
<p>Underneath the little drop down on "Build", there is an item called "Pre-actions". If you click into that and hit the "+" button down at the bottom you have the option to add a "Run Script Action". This means that you can run any arbitrary script before (or after) all builds (or runs or tests) of your app. And they can be different for different schemes. This seemed like a perfect way to accomplish my goal!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661814384661/TtBbzr_B5.png" alt="pre-build-actions.png" /></p>
<p>What I did was add an empty JSON file called "configuration.json" to my project and included it in the target. Then I put the "configuration-dev.json" and "configuration-prod.json" files into the directory of my app (so that it would be included in source control), but didn't add them to the project itself.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661814401233/X318H4bJ4.png" alt="folder-organization.png" /></p>
<p>Then, for my <code>dev</code> scheme I added this script as a pre-build action:</p>
<pre><code class="lang-bash">cp <span class="hljs-string">"<span class="hljs-variable">$SRCROOT</span>/configuration-dev.json"</span> <span class="hljs-string">"<span class="hljs-variable">$SRCROOT</span>/configuration.json"</span>
</code></pre>
<p>If you're not familiar with bash, the <code>cp</code> command copies files from the source to the destination. In this case it'll copy the dev version of the configuration file to the one that I included in the project, overwriting whatever was there before.</p>
<p>I also made sure that "Provide build settings from" was set to my app target, instead of "None". This is necessary to get the <code>SRCROOT</code> in the script, which is the path to the directory where the project file lives.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661814409716/Wx0CWtWW4.png" alt="dev-action.png" /></p>
<p>Then I did the same thing for the <code>prod</code> action:</p>
<pre><code class="lang-bash">cp <span class="hljs-string">"<span class="hljs-variable">$SRCROOT</span>/configuration-prod.json"</span> <span class="hljs-string">"<span class="hljs-variable">$SRCROOT</span>/configuration.json"</span>
</code></pre>
<p>Now, any time I build the <code>dev</code> scheme, I have the contents of <code>configuration-dev</code> bundled into the app and when I build the <code>prod</code> scheme, I have the contents of <code>configuration-prod</code>.</p>
<p>Simple, right? You could obviously run a lot more complicated scripts here and do more if you needed to. But so far this one-line script seems to have solved this little problem I had and I haven't seen any downsides.</p>
]]></content:encoded></item><item><title><![CDATA[Stop Throwing Away Errors]]></title><description><![CDATA[We all know how it goes when we are trying to get something working. We want to get there as fast as we can and we get focused on the "happy path". Maybe we're not that familiar with the technology/language/concept, but we have one specific goal or t...]]></description><link>https://dilloncodes.com/stop-throwing-away-errors</link><guid isPermaLink="true">https://dilloncodes.com/stop-throwing-away-errors</guid><category><![CDATA[Swift]]></category><category><![CDATA[iOS]]></category><category><![CDATA[Xcode]]></category><category><![CDATA[debugging]]></category><dc:creator><![CDATA[Dillon McElhinney]]></dc:creator><pubDate>Sat, 27 Aug 2022 17:11:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1661635996952/LlB9YymxHf.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We all know how it goes when we are trying to get something working. We want to get there as fast as we can and we get focused on the "happy path". Maybe we're not that familiar with the technology/language/concept, but we have one specific goal or task we are trying to achieve and we don't care so much about the edge cases or what will happen when things go wrong.</p>
<p>In an exploratory phase this is usually fine. Maybe you're writing a little script to solve some problem for yourself and no one else has to deal with it. Maybe you're just trying to figure out how something works and this code will never see the light of day anyways. Whatever the reason, it is probably ok to not handle all the edge cases and errors at first.</p>
<p>But one place I see a lot of newer iOS developers get stuck is when they think they've got the code for the happy path written, they go to run it, and it just doesn't work. </p>
<p>I had a psychology professor who used to say "When people don't know what to do, they do what they know how to do." He was saying it in the context of people with various addictions, but it is true of all people, and this is another good example. When newer devs get stuck here, they will reach for tools they are familiar with. They will add print statements and set break points and try to narrow down where the problem is. Sometimes this helps. It depends on where the bug is coming from. But there are many types of bugs that these tools are not particularly helpful in solving. One of those areas is in decoding.</p>
<p>I have lost count of the number of newer developers I have helped figure out decoding bugs, and I often see the same mistake. <strong>They throw away errors that would help them track down the bug.</strong> In this article we're going to look at some decoding bugs. They are a good example of a class of bug where the errors that are thrown are often the best tool for understanding what has gone wrong. It also happens to be a context where it is fairly easy to show a few specific examples, so it works well for an article of this size. But the message is true everywhere. Errors and the messages they carry are there for a reason and they are often a helpful tool for finding bugs and making your code more robust.</p>
<p>So don't throw them away!</p>
<p>Let's look at a specific example. To protect the egos of the various people I have helped with decoding bugs over the years (at least one of whom now works at Apple), I have made a sample project instead of using someone's actual code. But it is illustrative of the exact kinds of issues I have seen people run into in the real world.</p>
<h2 id="heading-sample-app-overview">Sample App Overview</h2>
<p>My sample app lets you look up random characters from the television show "Bob's Burgers". You can tap a button and it will load in a random character, show you their picture, their name, occupation and who voiced the character. There is also a link to the fandom wiki for that character. It uses <a target="_blank" href="https://bobs-burgers-api-ui.herokuapp.com">this api</a> to fetch the information. Here's how it is organized:</p>
<p>There is a character model:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Character</span>: <span class="hljs-title">Codable</span> </span>{
    <span class="hljs-keyword">let</span> id: <span class="hljs-type">Int</span>
    <span class="hljs-keyword">let</span> name: <span class="hljs-type">String</span>
    <span class="hljs-keyword">let</span> image: <span class="hljs-type">URL</span>
    <span class="hljs-keyword">let</span> occupation: <span class="hljs-type">String</span>
    <span class="hljs-keyword">let</span> voicedBy: <span class="hljs-type">String</span>
    <span class="hljs-keyword">let</span> wikiUrl: <span class="hljs-type">URL</span>

    <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">CodingKeys</span>: <span class="hljs-title">String</span>, <span class="hljs-title">CodingKey</span> </span>{
        <span class="hljs-keyword">case</span> id, name, image, occupation
        <span class="hljs-keyword">case</span> voicedBy = <span class="hljs-string">"voiced_by"</span>
        <span class="hljs-keyword">case</span> wikiUrl = <span class="hljs-string">"wiki_url"</span>
    }
}
</code></pre>
<p>There is a class that encapsulates access to the api with this interface:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BobsBurgersApi</span> </span>{
    <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> shared = <span class="hljs-type">BobsBurgersApi</span>()

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fetchCharacter</span><span class="hljs-params">(id: Int)</span></span> async -&gt; <span class="hljs-type">Character?</span> { ... }
}
</code></pre>
<p>There is a view model that interacts with the api and provides the primitive building blocks for the view to display:</p>
<pre><code class="lang-swift">@<span class="hljs-type">MainActor</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CharacterViewModel</span>: <span class="hljs-title">ObservableObject</span> </span>{

    @<span class="hljs-type">Published</span> <span class="hljs-keyword">var</span> character: <span class="hljs-type">Character?</span>

    <span class="hljs-keyword">var</span> title: <span class="hljs-type">String</span> { character?.name ?? <span class="hljs-string">""</span> }
    <span class="hljs-keyword">var</span> subtitle: <span class="hljs-type">String</span> { character?.occupation ?? <span class="hljs-string">""</span> }
    <span class="hljs-keyword">var</span> detail: <span class="hljs-type">String</span> { (character?.voicedBy).<span class="hljs-built_in">map</span> { <span class="hljs-string">"Voiced by: \($0)"</span> } ?? <span class="hljs-string">""</span> }
    <span class="hljs-keyword">var</span> learnMore: <span class="hljs-type">URL</span> { character?.wikiUrl ?? <span class="hljs-type">URL</span>(string: <span class="hljs-string">"https://bobs-burgers.fandom.com"</span>)! }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> api: <span class="hljs-type">BobsBurgersApi</span> { .shared }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">changeCharacter</span><span class="hljs-params">()</span></span> {
        <span class="hljs-type">Task</span> {
            character = await api.fetchCharacter(id: .random(<span class="hljs-keyword">in</span>: <span class="hljs-number">1</span>...<span class="hljs-number">501</span>))
        }
    }
}
</code></pre>
<p>And finally there is a <code>SwiftUI</code> view that shows the information:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">CharacterView</span>: <span class="hljs-title">View</span> </span>{
    @<span class="hljs-type">StateObject</span> <span class="hljs-keyword">var</span> viewModel = <span class="hljs-type">CharacterViewModel</span>()

    <span class="hljs-keyword">var</span> body: some <span class="hljs-type">View</span> {
        <span class="hljs-type">VStack</span> {
            <span class="hljs-type">AsyncImage</span>(url: viewModel.character?.image) { phase <span class="hljs-keyword">in</span>
                phase.image?.resizable()
            }
            .aspectRatio(contentMode: .fit)
            .frame(width: <span class="hljs-number">300</span>, height: <span class="hljs-number">300</span>, alignment: .center)
            <span class="hljs-type">Spacer</span>()
            <span class="hljs-type">Text</span>(viewModel.title)
                .font(.title)
            <span class="hljs-type">Text</span>(viewModel.subtitle)
            <span class="hljs-type">Text</span>(viewModel.detail)
                .font(.caption)
            <span class="hljs-keyword">if</span> !viewModel.title.isEmpty {
                <span class="hljs-type">Link</span>(<span class="hljs-string">"Learn More"</span>, destination: viewModel.learnMore)
                    .font(.caption)
            }
            <span class="hljs-type">Button</span>(<span class="hljs-string">"New Character"</span>) {
                viewModel.changeCharacter()
            }.padding()
        }
        .multilineTextAlignment(.center)
        .padding()
    }
}
</code></pre>
<p><em>I'm only loosely familiar with SwiftUI and I used this opportunity to explore it a little bit, so I am sure there are better/more idiosyncratic/efficient ways to write this view, but it isn't really the point of this article. Any SwiftUI experts out there, feel free to let me know how I could improve this. If I like it better than what I've written I'll up date this article and credit you (if you would like me to).</em></p>
<h2 id="heading-fixing-the-decoding">Fixing the Decoding</h2>
<p>Now that you have an overview of the app as a whole, let's take a deeper look at how the decoding is working currently. Here's where we're starting:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// in BobsBurgersApi</span>
<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> baseUrl = <span class="hljs-type">URL</span>(string: <span class="hljs-string">"https://bobsburgers-api.herokuapp.com"</span>)!

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fetchCharacter</span><span class="hljs-params">(id: Int)</span></span> async -&gt; <span class="hljs-type">Character?</span> {
    <span class="hljs-keyword">let</span> url = <span class="hljs-type">Self</span>.baseUrl
        .appendingPathComponent(<span class="hljs-string">"character"</span>)
        .appendingPathComponent(<span class="hljs-string">"\(id)"</span>)
    <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> (data, <span class="hljs-number">_</span>) = <span class="hljs-keyword">try</span>? await <span class="hljs-type">URLSession</span>.shared.data(from: url) <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
    }
    <span class="hljs-keyword">let</span> character = <span class="hljs-keyword">try</span>? <span class="hljs-type">JSONDecoder</span>().decode(<span class="hljs-type">Character</span>.<span class="hljs-keyword">self</span>, from: data)
    <span class="hljs-keyword">return</span> character
}
</code></pre>
<p>Very often I will come across code that is written like this, at least at first. The logic is fairly straightforward.</p>
<ol>
<li>It builds the url by appending "character" and the given id to the api's base url</li>
<li>It tries to get the data from that url</li>
<li>It tries to make a <code>Character</code> from that data </li>
<li>If it is successful, it returns that character otherwise it returns <code>nil</code></li>
</ol>
<p>Right now, we'll find that this always fails to load a character and we don't get any feedback. So, reaching for familiar tools, we might add print statements like this:</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fetchCharacter</span><span class="hljs-params">(id: Int)</span></span> async -&gt; <span class="hljs-type">Character?</span> {
    <span class="hljs-keyword">let</span> url = <span class="hljs-type">Self</span>.baseUrl
        .appendingPathComponent(<span class="hljs-string">"character"</span>)
        .appendingPathComponent(<span class="hljs-string">"\(id)"</span>)
    <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> (data, <span class="hljs-number">_</span>) = <span class="hljs-keyword">try</span>? await <span class="hljs-type">URLSession</span>.shared.data(from: url) <span class="hljs-keyword">else</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Couldn't fetch data"</span>)
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
    }
    <span class="hljs-keyword">let</span> character = <span class="hljs-keyword">try</span>? <span class="hljs-type">JSONDecoder</span>().decode(<span class="hljs-type">Character</span>.<span class="hljs-keyword">self</span>, from: data)
    <span class="hljs-keyword">if</span> character == <span class="hljs-literal">nil</span> { <span class="hljs-built_in">print</span>(<span class="hljs-string">"Couldn't decode character"</span>) }
    <span class="hljs-keyword">return</span> character
}

<span class="hljs-comment">// prints: Couldn't decode character</span>
</code></pre>
<p>This helps us a tiny bit, because now we can see that it is the decoding that is failing and not fetching the data, but it doesn't tell us anything about <em>why</em> it is failing. Next, we could try printing the data itself out with something like this:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">if</span> character == <span class="hljs-literal">nil</span> { <span class="hljs-built_in">print</span>(<span class="hljs-type">String</span>(data: data, encoding: .utf8)!) }

<span class="hljs-comment">// prints: {"error":"Error while retreiving data with id 157 in route character."}</span>
</code></pre>
<p>Again this is a little more helpful. And in this case, it is enough to track down the problem. The api is not returning a character model. This is not a decoding problem, but either a problem with the server or with the request we are making. Looking back at <a target="_blank" href="https://bobs-burgers-api-ui.herokuapp.com/documentation#characters">the documentation</a> I notice that the route is actually <code>/characters/:id</code>, not <code>character/:id</code>. So we update our code to use "characters" and now it prints:</p>
<pre><code>{<span class="hljs-attr">"id"</span>:<span class="hljs-number">3</span>,<span class="hljs-attr">"name"</span>:<span class="hljs-string">"Adam"</span>,<span class="hljs-attr">"image"</span>:<span class="hljs-string">"https://bobsburgers-api.herokuapp.com/images/characters/3.jpg"</span>,<span class="hljs-attr">"gender"</span>:<span class="hljs-string">"Male"</span>,<span class="hljs-attr">"hairColor"</span>:<span class="hljs-string">"Brown"</span>,<span class="hljs-attr">"relatives"</span>:[{<span class="hljs-attr">"name"</span>:<span class="hljs-string">"Unnamed wife"</span>}],<span class="hljs-attr">"firstEpisode"</span>:<span class="hljs-string">"\"Mr. Lonely Farts\""</span>,<span class="hljs-attr">"voicedBy"</span>:<span class="hljs-string">"Brian Huskey"</span>,<span class="hljs-attr">"url"</span>:<span class="hljs-string">"https://bobsburgers-api.herokuapp.com/characters/3"</span>,<span class="hljs-attr">"wikiUrl"</span>:<span class="hljs-string">"https://bobs-burgers.fandom.com/wiki/Adam"</span>}
</code></pre><p>So now we get an actual character model back, but the decoding is still failing. What is the problem?</p>
<p>We could look through the keys and values one by one and make sure they line up with the <code>Character</code> struct that we have defined. This could work in this context because it is a relatively small model, but it can get very cumbersome if you are fetching a long list or a large/complex model. A better way (and the point of this whole article) is to use the error we get to give us some clues. So lets modify our <code>fetchCharacter</code> function to catch those errors instead of throwing them away and turning things in to optionals. Now it looks like this:</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fetchCharacter</span><span class="hljs-params">(id: Int)</span></span> async -&gt; <span class="hljs-type">Character?</span> {
    <span class="hljs-keyword">let</span> url = <span class="hljs-type">Self</span>.baseUrl
        .appendingPathComponent(<span class="hljs-string">"characters"</span>)
        .appendingPathComponent(<span class="hljs-string">"\(id)"</span>)
    <span class="hljs-keyword">do</span> {
        <span class="hljs-keyword">let</span> (data, <span class="hljs-number">_</span>) = <span class="hljs-keyword">try</span> await <span class="hljs-type">URLSession</span>.shared.data(from: url)
        <span class="hljs-keyword">let</span> character = <span class="hljs-keyword">try</span> <span class="hljs-type">JSONDecoder</span>().decode(<span class="hljs-type">Character</span>.<span class="hljs-keyword">self</span>, from: data)
        <span class="hljs-keyword">return</span> character
    } <span class="hljs-keyword">catch</span> {
        <span class="hljs-built_in">print</span>(error)
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
    }
}
<span class="hljs-comment">// note that this code is basically the same amount of code as we had before</span>
<span class="hljs-comment">// so don't try to use concision as a reason throw away errors</span>
</code></pre>
<p>And it prints this when we run it:</p>
<pre><code><span class="hljs-selector-tag">keyNotFound</span>(CodingKeys(<span class="hljs-attribute">stringValue</span>: <span class="hljs-string">"voiced_by"</span>, <span class="hljs-attribute">intValue</span>: nil), Swift.DecodingError.Context(<span class="hljs-attribute">codingPath</span>: [], <span class="hljs-attribute">debugDescription</span>: <span class="hljs-string">"No value associated with key CodingKeys(stringValue: \"</span>voiced_by\<span class="hljs-string">", intValue: nil) (\"</span>voiced_by\<span class="hljs-string">")."</span>, <span class="hljs-attribute">underlyingError</span>: nil))
</code></pre><p>This may look intimidating (and that might be the reason many newer developers discard these errors), but it isn't as bad as it may seem and it is here to help. The error is <code>keyNotFound</code> and if we look a little further we see that the key it couldn't find is <code>"voiced_by"</code>. So if we hop back over to <a target="_blank" href="https://bobs-burgers-api-ui.herokuapp.com/documentation#characters">the documentation</a> and scroll down to the "Character Schema", we see that the key is actually called <code>"voicedBy"</code>. At the same time we might notice that the one we have defined as <code>"wiki_url"</code> is actually <code>"wikiUrl"</code>. (If we didn't happen to notice, the error would tell us the next time we ran it.)</p>
<p>So let's fix that, and re-run. In this case we can actually just get rid of the <code>CodingKeys</code> enum because all of our property names match the keys in the   JSON.</p>
<pre><code class="lang-swift"><span class="hljs-comment">//enum CodingKeys: String, CodingKey {</span>
<span class="hljs-comment">//    case id, name, image, occupation</span>
<span class="hljs-comment">//    case voicedBy = "voiced_by"</span>
<span class="hljs-comment">//    case wikiUrl = "wiki_url"</span>
<span class="hljs-comment">//}</span>
</code></pre>
<p>Now we can re-run and see some actual characters on screen!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661619913356/Cg3E5BFWY.png" alt="bobs-characters.png" /></p>
<p>However, if you tap through characters long enough, you'll find that some of them fail to decode. Let's see what the errors have to say:</p>
<pre><code><span class="hljs-selector-tag">keyNotFound</span>(CodingKeys(<span class="hljs-attribute">stringValue</span>: <span class="hljs-string">"voicedBy"</span>, <span class="hljs-attribute">intValue</span>: nil), Swift.DecodingError.Context(<span class="hljs-attribute">codingPath</span>: [], <span class="hljs-attribute">debugDescription</span>: <span class="hljs-string">"No value associated with key CodingKeys(stringValue: \"</span>voicedBy\<span class="hljs-string">", intValue: nil) (\"</span>voicedBy\<span class="hljs-string">")."</span>, <span class="hljs-attribute">underlyingError</span>: nil))
</code></pre><p>Interesting. It is the same error as before, only now we know that we have the key name right. Back to <a target="_blank" href="https://bobs-burgers-api-ui.herokuapp.com/documentation#characters">the documentation</a>! Down in the "Character Schema" section we can see:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Key</td><td>Type</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td>voicedBy</td><td>string / undefined</td><td>The voice actor for the character</td></tr>
</tbody>
</table>
</div><p>That is this api's way of communicating that <code>voicedBy</code> will be a <code>String</code> if there is one, or it will be undefined (or <code>nil</code>) if there isn't. In Swift that is actually a different type that we call an optional string (<code>String?</code>, or <code>Optional&lt;String&gt;</code>), so let's update our model. While we're at it, let's check if anything else should be optional.</p>
<p>The only other one I see is <code>occupation</code>, so we'll update that one as well. Now our model looks like this:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Character</span>: <span class="hljs-title">Codable</span> </span>{
    <span class="hljs-keyword">let</span> id: <span class="hljs-type">Int</span>
    <span class="hljs-keyword">let</span> name: <span class="hljs-type">String</span>
    <span class="hljs-keyword">let</span> image: <span class="hljs-type">URL</span>
    <span class="hljs-keyword">let</span> occupation: <span class="hljs-type">String?</span>
    <span class="hljs-keyword">let</span> voicedBy: <span class="hljs-type">String?</span>
    <span class="hljs-keyword">let</span> wikiUrl: <span class="hljs-type">URL</span>
}
</code></pre>
<p>Now, we can tap through Bob's Burger's characters to our heart's content and the decoding should never fail. But if it did, we'd still be printing that error to the console and should be able to track down the issue quickly.</p>
<h2 id="heading-nested-example">Nested Example</h2>
<p>I noticed in the docs that some characters have a <code>relatives</code> array. If a certain character has known relatives who are also in the system, it will return them in an array of a slimmed down character model. That seems like interesting information, so let's add it to our app.</p>
<p>I'll call this version of the model <code>Relative</code> to make it clear what we're dealing with:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">Character</span> </span>{
    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Relative</span>: <span class="hljs-title">Codable</span> </span>{
        <span class="hljs-keyword">let</span> name: <span class="hljs-type">String</span>
        <span class="hljs-keyword">let</span> wikiUrl: <span class="hljs-type">URL</span>
        <span class="hljs-keyword">let</span> url: <span class="hljs-type">URL</span>
    }
}

<span class="hljs-comment">// in Character struct</span>
<span class="hljs-keyword">let</span> relatives: [<span class="hljs-type">Relative</span>]?
</code></pre>
<p>Then, so we can see the relatives on screen, I'll add a new property to the view model and display it on the view:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// in CharacterViewModel</span>
<span class="hljs-keyword">var</span> subtitle2: <span class="hljs-type">String</span> {
    <span class="hljs-keyword">let</span> relatives = character?.relatives?.<span class="hljs-built_in">map</span>(\.name).joined(separator: <span class="hljs-string">", "</span>)
    <span class="hljs-keyword">return</span> relatives.<span class="hljs-built_in">map</span> { <span class="hljs-string">"Relatives: \($0)"</span> } ?? <span class="hljs-string">""</span>
}

<span class="hljs-comment">// in CharacterView, after subtitle</span>
<span class="hljs-keyword">if</span> !viewModel.subtitle2.isEmpty {
    <span class="hljs-type">Text</span>(viewModel.subtitle2)
}
</code></pre>
<p>Now, if you tap through characters, you'll find that many of them don't have relatives but the ones that do will show up in that list. Occasionally though, you'll find one that will fail decoding and it will print something like this error:</p>
<pre><code><span class="hljs-selector-tag">keyNotFound</span>(CodingKeys(<span class="hljs-attribute">stringValue</span>: <span class="hljs-string">"wikiUrl"</span>, <span class="hljs-attribute">intValue</span>: nil), Swift.DecodingError.Context(<span class="hljs-attribute">codingPath</span>: [CodingKeys(<span class="hljs-attribute">stringValue</span>: <span class="hljs-string">"relatives"</span>, <span class="hljs-attribute">intValue</span>: nil), _JSONKey(<span class="hljs-attribute">stringValue</span>: <span class="hljs-string">"Index 0"</span>, <span class="hljs-attribute">intValue</span>: <span class="hljs-number">0</span>)], <span class="hljs-attribute">debugDescription</span>: <span class="hljs-string">"No value associated with key CodingKeys(stringValue: \"</span>wikiUrl\<span class="hljs-string">", intValue: nil) (\"</span>wikiUrl\<span class="hljs-string">")."</span>, <span class="hljs-attribute">underlyingError</span>: nil))
</code></pre><p>It may take a little more effort to get to the bottom of what this error is talking about. It says that it can't find a <code>wikiUrl</code> and we might be tempted to think that is the <code>wikiUrl</code> on our <code>Character</code> model, because it has one of those. But if we keep reading, we can see in the <code>DecodingError.Context</code> that it is looking at the key <code>relatives</code> (which we know is an array), at the 0th index item (meaning the first item in the array), and there it can't find a key called <code>wikiUrl</code>. That means that the first relative in our array doesn't have a <code>wikiUrl</code>. </p>
<p>This is an interesting one because <a target="_blank" href="https://bobs-burgers-api-ui.herokuapp.com/documentation#characters">the docs</a> say that <code>wikiUrl</code> is non-optional, but apparently it is. I printed out the json for this character and here is what it returns for <code>relatives</code>: </p>
<pre><code><span class="hljs-string">"relatives"</span> : [
    {
        <span class="hljs-string">"name"</span> : <span class="hljs-string">"Unnamed child"</span>
    }
]
</code></pre><p>Apparently, there is at least one <code>Relative</code> who doesn't have a <code>wikiUrl</code> or a <code>url</code>, which we can account for in our model by making those optional (or just not decoding them, since we aren't using them).</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Relative</span>: <span class="hljs-title">Codable</span> </span>{
    <span class="hljs-keyword">let</span> name: <span class="hljs-type">String</span>
    <span class="hljs-keyword">let</span> wikiUrl: <span class="hljs-type">URL?</span>
    <span class="hljs-keyword">let</span> url: <span class="hljs-type">URL?</span>
}

<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Relative</span>: <span class="hljs-title">Codable</span> </span>{
    <span class="hljs-keyword">let</span> name: <span class="hljs-type">String</span>
<span class="hljs-comment">//    let wikiUrl: URL</span>
<span class="hljs-comment">//    let url: URL</span>
}
</code></pre>
<p><em>This is a good reminder that the docs are a good place to start, but they aren't always up to date. If you have access to the developer who is maintaining the backend, it would be good to alert them to the discrepancy so they can either bring the backend logic up to spec or update the docs to match the actual logic. Or, if it is an open source API, you could open an Issue and/or make a contribution!</em></p>
<p>You can see from this slightly more complex example that these errors are helpful for getting to the root issue even if the problem is deeply nested in your JSON. They may not be in the most human-readable form, but it is possible to work your way down them and gain valuable insights. And, as with most things, you'll get better with practice and will eventually be able to skim them to get everything you need.</p>
<h2 id="heading-wrap-up">Wrap up</h2>
<p>The examples in this simple app may seem trivial, but they are reflective of the sort of debugging that is common to work through when you're trying to get a new network interface up and running. I have seen people spend lots of time trying to work out what is wrong with their decoding and come up with all sorts of odd and complex ways to try to pinpoint the problem. But as we have looked at today, errors, while they might seem intimidating and hard to read at first, are often full of helpful information that tells us exactly what the problem is.</p>
<p>So next time you're tempted to throw that error away, <a target="_blank" href="https://media.giphy.com/media/IRkqguqMTKUne/giphy.gif">don't!</a></p>
<p>Let me know your opinions on errors and when/when not to use them in the comments. And find the full starter project on <a target="_blank" href="https://github.com/dillon-mce/dont-throw-away-errors/tree/starter-project">this branch</a> and the full finished product on <a target="_blank" href="https://github.com/dillon-mce/dont-throw-away-errors">this branch</a>.</p>
<h2 id="heading-post-scripts">Post Scripts</h2>
<p>Not all errors are as helpful in debugging your code as they are in the context of decoding. Just as a screw driver is very helpful for driving screws, but less so for driving nails, errors are helpful for tracking down and fixing some problems and less so for others. My intention here was just to point out that they <em>are</em> a useful tool and that they are often overlooked by newer developers. Part of growing as a developer is learning the tools that are available to you and over time you will develop a sense of which one(s) will be helpful in different situations.</p>
<p>Another thing that we did not explore in this article is when/how to communicate to the user that some error has occurred. This will depend on your content/context and a little bit on personal taste. But generally speaking it is good practice to throw errors up the chain at least to the layer where your business logic lives. That way you at least have the information there and can make decisions about how to handle it in ways that make sense for your app.</p>
]]></content:encoded></item><item><title><![CDATA[5 Reasons To Use Typealias]]></title><description><![CDATA[Swift has many features that allow us as the users of the language to customize how we write code, while still giving us all the protections of a statically typed language. Today we're going to talk about one of those features which seems small, but ...]]></description><link>https://dilloncodes.com/reasons-to-use-typealias</link><guid isPermaLink="true">https://dilloncodes.com/reasons-to-use-typealias</guid><category><![CDATA[Swift]]></category><category><![CDATA[Developer]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[iOS]]></category><category><![CDATA[Xcode]]></category><dc:creator><![CDATA[Dillon McElhinney]]></dc:creator><pubDate>Wed, 25 May 2022 16:52:59 GMT</pubDate><content:encoded><![CDATA[<p>Swift has many features that allow us as the users of the language to customize how we write code, while still giving us all the protections of a statically typed language. Today we're going to talk about one of those features which seems small, but it packs a lot of punch.</p>
<p>The <code>typealias</code> keyword lets you define a "type alias", a custom name for a class, struct or any other existing type in Swift. Once you declare it you can use that new name anywhere you would have used the name of the original type.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">typealias</span> <span class="hljs-type">NewName</span> = <span class="hljs-type">ExistingType</span>

<span class="hljs-keyword">let</span> newName = <span class="hljs-type">NewName</span>() <span class="hljs-comment">// the type of newName will actually be ExistingType</span>
</code></pre>
<p>Simple, right? Basically you can give your type a nickname.</p>
<h2 id="heading-reasons-to-use-it">Reasons to use it</h2>
<p>We’ve all had nicknames and used them for our friends and family, but unlike your childhood nicknames, type aliases can provide a lot of benefit to you as a developer. Here’s a list of the top five.</p>
<h3 id="heading-1-clarity">1. Clarity</h3>
<p>One use for type aliases is to add clarity around what a type is used for. <code>Double</code> is a primitive type in Swift and it can be used in any number of ways, but when it is used in the context of dates and times it might be good to add a little more explanation about what we <em>mean</em> with the value. Foundation does exactly that by adding a type alias called <code>TimeInterval</code> and documenting that "A <code>TimeInterval</code> value is always specified in seconds…". </p>
<p>Another good example comes from handling money. It is relatively common practice when dealing with money in code is to multiply the value by 100 and work with it as <code>Int</code> values to avoid rounding errors. If that is what you're doing, you might add some clarity to your code by adding a <code>Cents</code> type alias to communicate what you mean with the <code>Int</code> value.</p>
<pre><code class="lang-swift"><span class="hljs-comment">/// Describes the value of one-hundreth (1/100) of a dollar (USD).</span>
<span class="hljs-keyword">typealias</span> <span class="hljs-type">Cents</span> = <span class="hljs-type">Int</span>

<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Product</span> </span>{
    <span class="hljs-keyword">let</span> name: <span class="hljs-type">String</span>
    <span class="hljs-keyword">let</span> price: <span class="hljs-type">Cents</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">buy</span><span class="hljs-params">(product: Product, currentBalance: Cents)</span></span> <span class="hljs-keyword">throws</span> {
    <span class="hljs-keyword">guard</span> currentBalance &gt; product.price <span class="hljs-keyword">else</span> { <span class="hljs-keyword">throw</span> .<span class="hljs-type">NotEnoughMoney</span> }
    currentBalance -= product.price
    currentUser.add(product)
}
</code></pre>
<p>That's a lot clearer than having <code>Int</code> all over the place and either adding comments to describe the meaning or assuming that the reader knows what it means. (You know <a target="_blank" href="https://media.giphy.com/media/l1J9yApnGts8EXYoo/giphy-downsized-large.gif">what happens when you assume.</a>)</p>
<h3 id="heading-2-brevity">2.  Brevity</h3>
<p>Another use is to take a long name and make it shorter. If you've ever worked with <code>AVFoundation</code> for instance, you'll know that there are some incredibly long delegate names out there (e.g. <code>AVCaptureFileOutputRecordingDelegate</code>) and it can be nice to provide a short alternative. Or maybe you are trying to keep the line width to a reasonable limit and the completion handler definition is too long to fit.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// Say you have this function that you are trying to make shorter</span>
<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fetchFavorites</span><span class="hljs-params">(type: ItemType, completion: @escaping <span class="hljs-params">(Result&lt;[Item], Error&gt;)</span></span></span> -&gt; <span class="hljs-type">Void</span>)

<span class="hljs-comment">// you can typealias the result type</span>
<span class="hljs-keyword">typealias</span> <span class="hljs-type">ItemResult</span> = <span class="hljs-type">Result</span>&lt;[<span class="hljs-type">Item</span>], <span class="hljs-type">Error</span>&gt;

<span class="hljs-comment">// and you can typealias the closure type</span>
<span class="hljs-keyword">typealias</span> <span class="hljs-type">ItemHandler</span> = (<span class="hljs-type">ItemResult</span>) -&gt; <span class="hljs-type">Void</span>

<span class="hljs-comment">// then your function could look like this</span>
<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fetchFavorites</span><span class="hljs-params">(type: ItemType, completion: @escaping ItemHandler)</span></span>
</code></pre>
<p>Much shorter! Taking 98 characters down to 78.</p>
<h3 id="heading-3-dry-dont-repeat-yourself">3. DRY (Don't Repeat Yourself)</h3>
<p>That leads to another good reason to use <code>typealias</code>. If you define a type alias for a specific result type, you can reuse that everywhere you need that result type. Maybe you have a function that gets <em>all</em> the items of a type, or <em>all</em> the items that belong to a user. Both of those could use this same type alias.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fetchAll</span><span class="hljs-params">(type: ItemType, completion: @escaping ItemHandler)</span></span>
<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fetchAll</span><span class="hljs-params">(<span class="hljs-keyword">for</span> user: User, compeltion: @escaping ItemHandler)</span></span>
</code></pre>
<p>On top of that, the alias in this context makes it easier to make changes down the line. Maybe your backend folks want to change the returned result from an array of items to an object that contains some other information alongside the array. All you need to do is update the type alias and the compiler will then show you where you need to handle it.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">typealias</span> <span class="hljs-type">ItemResult</span> = <span class="hljs-type">Result</span>&lt;<span class="hljs-type">ItemResponse</span>, <span class="hljs-type">Error</span>&gt;
</code></pre>
<h3 id="heading-4-combine-protocols">4. Combine Protocols</h3>
<p>You can also use <code>typealias</code> to combine multiple protocols into one name. You may want to do this if you have multiple protocols that are commonly used together, but that define methods that aren't required to be handled by the same type. A good example of this from Foundation is <code>Codable</code>, which is really just a type alias for <code>Decodable</code> and <code>Encodable</code>. This way if your type can just be encodable or decodable if you only need the functionality of one or the other, or it can be <em>both</em> by adopting the one protocol.</p>
<p>Maybe in your code the <code>UICollectionViewDelegate</code> and <code>UICollectionViewDataSource</code> are always the same object. You could define a type alias to encompass both. Same for <code>UICollectionViewDragDelegate</code> and <code>UICollectionViewDropDelegate</code>.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">typealias</span> <span class="hljs-type">CollectionViewDataSourceDelegate</span> = <span class="hljs-type">UICollectionViewDataSource</span> &amp; <span class="hljs-type">UICollectionViewDelegate</span>

<span class="hljs-keyword">typealias</span> <span class="hljs-type">CollectionViewDragDropDelegate</span> = <span class="hljs-type">UICollectionViewDragDelegate</span> &amp; <span class="hljs-type">UICollectionViewDropDelegate</span>

<span class="hljs-keyword">typealias</span> <span class="hljs-type">CollectionViewSuperDelegate</span> = <span class="hljs-type">CollectionViewDataSourceDelegate</span> &amp; <span class="hljs-type">CollectionViewDragDropDelegate</span>

<span class="hljs-comment">// Use the type alias that combines them all</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ViewController</span>: <span class="hljs-title">CollectionViewSuperDelegate</span> </span>{
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">viewDidLoad</span> </span>{
        <span class="hljs-keyword">super</span>.viewDidLoad()

        <span class="hljs-comment">// and then this type can be all four of these things</span>
        collectionView.dataSource = <span class="hljs-keyword">self</span>
        collectionView.delegate = <span class="hljs-keyword">self</span>
        collectionView.dragDelegate = <span class="hljs-keyword">self</span>
        collectionView.dropDelegate = <span class="hljs-keyword">self</span>
    }
}
</code></pre>
<h3 id="heading-5-better-auto-complete">5. Better Auto-complete</h3>
<p>Finally, if you're lazy like me and rely on auto-complete to do most of your typing for you, you're in luck. Type aliases will show up there and definitely speed things up when they are specifying a generic type. <code>ItemResult</code> will auto-complete as a whole unit, but <code>Result&lt;[Item], Error&gt;</code> would take more keystrokes because you would either have to auto-complete <code>Result</code>, <code>Item</code> and <code>Error</code> separately, or just type it all out yourself.</p>
<h3 id="heading-bonus-light-weight-type-replacement">Bonus. Light-weight Type Replacement</h3>
<p>With a type alias, you can name a tuple with labels and it will basically function as a <code>struct</code> without a lot of the bells and whistles. <strong>I would not recommend doing this in production.</strong> It can be helpful when you're prototyping, messing around with a script or <em>maybe</em> in a test suite. But in almost all cases you should turn your prototype into a real <code>struct</code>, or pick an existing type that fits the shape of your data, before pushing your code to production.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// this is valid Swift</span>
<span class="hljs-comment">// you can name this tuple</span>
<span class="hljs-keyword">typealias</span> <span class="hljs-type">Coordinate</span> = (x: <span class="hljs-type">Int</span>, y: <span class="hljs-type">Int</span>)

<span class="hljs-comment">// use it as a parameter in your functions</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">manhattanDistance</span><span class="hljs-params">(from first: Coordinate, to second: Coordinate)</span></span> -&gt; <span class="hljs-type">Int</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">abs</span>(first.x - second.x) + <span class="hljs-built_in">abs</span>(first.y - second.y)
}

<span class="hljs-comment">// and pass literal tuples to that function</span>
manhattanDistance(from: (<span class="hljs-number">1</span>,<span class="hljs-number">2</span>), to: (<span class="hljs-number">3</span>,<span class="hljs-number">4</span>)) <span class="hljs-comment">// results in 4</span>

<span class="hljs-comment">// but there is already a common type in most contexts where you write Swift</span>
<span class="hljs-comment">// called CGPoint that you should probably use instead</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">manhattanDistance</span><span class="hljs-params">(from first: CGPoint, to second: CGPoint)</span></span> -&gt; <span class="hljs-type">CGFloat</span> {…}
</code></pre>
<h2 id="heading-why-you-mostly-shouldnt-use-it">Why You (Mostly) Shouldn't Use It</h2>
<p>Type aliases are very useful and there are many good reasons to use them, but that doesn't mean you should rename every type in your code. So next time you're going to reach for it, be sure to weigh the cost against the benefits.</p>
<p>Type aliases add complexity to your code. Mental overhead. Any time a reader of your code (including future you) comes across an alias they will either have to option+click it in Xcode to see what it actually is, or they will have to switch it out for the real meaning in their head. If it is the first time they have encountered it, they may have to look up the actual definition several times before they internalize it. Sometimes this trade off is worth it, if you are reaping the benefits of one or more reasons listed above. But other times it is not.</p>
<p>You might find yourself in a situation where you want to rename something because of your preference, but you won't actually benefit from it, or the benefit is so small that it isn't worth the added complexity. I would advise against using a type alias in this case. Especially if you work on a codebase with a team larger than just you. It actually makes your code harder to read. And that is almost never a good thing. </p>
<pre><code class="lang-swift"><span class="hljs-comment">// this alias actually reduces clarity _and_ brevity</span>
<span class="hljs-keyword">typealias</span> <span class="hljs-type">Number</span> = <span class="hljs-type">Int</span>

<span class="hljs-comment">// this one adds brevity but reduces clarity</span>
<span class="hljs-keyword">typealias</span> <span class="hljs-type">TableController</span> = <span class="hljs-type">UITableViewController</span>

<span class="hljs-comment">// these don't add much benefit and are kind of the opposite of dry</span>
<span class="hljs-keyword">typealias</span> <span class="hljs-type">StringArray</span> = <span class="hljs-type">Array</span>&lt;<span class="hljs-type">String</span>&gt;
<span class="hljs-keyword">typealias</span> <span class="hljs-type">IntArray</span> = <span class="hljs-type">Array</span>&lt;<span class="hljs-type">Int</span>&gt;
<span class="hljs-keyword">typealias</span> <span class="hljs-type">DoubleArray</span> = <span class="hljs-type">Array</span>&lt;<span class="hljs-type">Double</span>&gt;
</code></pre>
<p>The cost gets even higher if you and/or your team spend a lot of time looking at your code outside of Xcode. If you have a regular code review process (you should) there is a good chance that significant amounts of time will be spent reading and reasoning about code for the first time from the GitHub/GitLab/Bitbucket/etc diff interface where you don't have the convenience of being able to option-click a type name to see its definition. There are times when the trade off is still worth it, but it is good to have a conversation with your team about what your collective line is, or to follow the established practice if it exists.</p>
<h2 id="heading-wrap-up">Wrap Up</h2>
<p>Type aliases are just one of the many features of Swift that makes it really customizable and extensible while still being totally type-safe. We've covered some of the many good reasons to use them, including adding clarity, reducing line-length, reducing unnecessary repetition, combining protocols, and improving auto-complete. And we've talked about how type aliases <em>always</em> come at the cost of added complexity, so we should weigh the cost against the benefit before we use them.</p>
<p>What do you think? When do you use type aliases in your code? What makes the trade off worth it for you?</p>
]]></content:encoded></item><item><title><![CDATA[Writing Short Code Isn't The Point]]></title><description><![CDATA[I don't know about you, but I have spent a lot of time trying to write code in the shortest, most elegant way possible. I have put a lot of thought and time and effort into it over the years. And I see it a lot in other people's code as well. I have ...]]></description><link>https://dilloncodes.com/writing-readable-code</link><guid isPermaLink="true">https://dilloncodes.com/writing-readable-code</guid><category><![CDATA[Swift]]></category><category><![CDATA[iOS]]></category><category><![CDATA[Developer]]></category><category><![CDATA[development]]></category><category><![CDATA[General Programming]]></category><dc:creator><![CDATA[Dillon McElhinney]]></dc:creator><pubDate>Mon, 14 Mar 2022 00:45:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/ofpr9Cw8Rj8/upload/v1647218280645/RpfZ129AN.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I don't know about you, but I have spent a lot of time trying to write code in the shortest, most elegant way possible. I have put a lot of thought and time and effort into it over the years. And I see it a lot in other people's code as well. I have seen it from both junior and senior developers. I have seen it in new code bases and mature code bases, both large and small. I think to some extent it comes from the code challenge and technical interview culture where accomplishing the goal with fewer characters is seen as better. Maybe it comes from our desire to prove ourselves and line count is a metric that is simply easy to gather. Maybe we just like the challenge, or we see it as a status symbol.</p>
<p>Whatever the reasons, I think we should stop it. We should strive to write understandable code instead. In this article we will take a look at how it is saves time, it is easier to share with other people, it is easier to fix and it is easier to modify. And then I'll share a few simple tactics for writing more readable code.</p>
<h2 id="heading-why">Why</h2>
<p>Almost all the reasons I could come up with boil down to the fact that it saves time to write longer, more understandable code. It saves you time. It saves time for everyone you work with. It saves time for your users. And time is our most valuable resource. It is one of the few things we cannot get more of, so let's try to make the most of it. </p>
<p>You read code more than you write it. Be mindful during almost any work day and you'll find that this is true. You spend time looking up existing functionality to answer your colleague's question. You spend a bunch of time trying to find the right spot to fix a bug, and then write the one line to fix it. You spend time planning where and how to add a new feature, and then some time writing it. But writing it isn't just a straight shot where you start at the first line and move on to each subsequent one. You reference docs or example code. You check against other parts of the code base to find reusable elements, and to make sure you're not breaking something. You read back over the code you've just written to make sure it does what you think it does. And so on.</p>
<p>So if you spend so much more time reading code than writing it, it makes sense to optimize for readability. Make it easier to reason about. Easier to load into your RAM when you come back to it in six months. Not only will this save you time, but it will also scale better. Other people have to understand your code. You have to understand other people's code. Would you rather spend six hours trying to tease out what a dense block of code actually does or be able to scan it, get the gist, and move on to adding your feature? Or even worse, maybe you didn't take the time to fully understand that dense block of code and you introduce a hard-to-find bug that will be the bane of your existence for the next year. (Not that this has ever happened to me 😁)</p>
<p>The more readable code is, the easier it is to debug. You are less likely to introduce bugs in the first place because they aren't obscured by the complexity. But with the ones that do get through, they are easier to track down because the flow of the logic is obvious. There is nowhere for them to hide.</p>
<p>Finally, the only constant is change. Your code base will change. The developers working on it will change. You will need to onboard new developers when they join the team and you will need to take ownership over code you didn't write when people leave. You will need to change functionality over time. Having readable code makes all of those things easier.</p>
<p>On top of that the language/libraries you are using will probably change over time and there is a high correlation between short/clever code and the most sugary of syntax. That cutting-edge syntax is way more likely to change over time than the fundamentals. This isn't a huge problem, but it is one more way that you are likely to waste some time if you insist on writing the shortest code.</p>
<h2 id="heading-do-this-instead">Do This Instead</h2>
<p>So how do we write this mythical "readable" code? There is no single thing you can do that will magically make your code readable. But there are many things you can do which will make it <em>more</em> readable. It starts with your mindset. Right now, when you sit down to write some code you may be in the habit of asking yourself something like this "how will I write this in the best/most efficient/most elegant/shortest way possible?" I know that was my mindset for a long time. But now I would advocate that we should instead ask ourselves "how can I write this in such a way that it will make the logic obvious and understandable?" Simply re-framing things in this way is a huge step in the right direction, but I've also got a few practical tips that I try to keep in mind along the way:</p>
<p><strong>Name variables for their role, not their type.</strong> Especially in strongly-typed languages like Swift, it isn't necessary to add type information to a variable's name. That information already lives somewhere. But the compiler can't really know what that variable is <em>for</em>. Why is it there? What does it contribute to this piece of code? Give future readers a huge clue by naming that variable in a way that communicates this information.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// don't do this</span>
<span class="hljs-keyword">let</span> redColor = <span class="hljs-type">UIColor</span>.red
<span class="hljs-keyword">let</span> array = [<span class="hljs-number">24</span>, <span class="hljs-number">31</span>, <span class="hljs-number">27</span>, <span class="hljs-number">45</span>, <span class="hljs-number">37</span>]

<span class="hljs-comment">// do this instead</span>
<span class="hljs-keyword">let</span> error = <span class="hljs-type">UIColor</span>.red
<span class="hljs-keyword">let</span> userAges = [<span class="hljs-number">24</span>, <span class="hljs-number">31</span>, <span class="hljs-number">27</span>, <span class="hljs-number">45</span>, <span class="hljs-number">37</span>]
</code></pre>
<p><strong>Do one thing at a time.</strong> Break code up into discrete pieces of logic. I often come across code where someone is trying to save time or improve performance by mashing multiple pieces of logic into one loop or something. There may be places where that becomes necessary for performance reasons, but it is very difficult to estimate performance of software ahead of time and in my experience developers are pretty bad at estimating the actual causes of performance bugs. It is much better to write it the clear and slow way first, and then test to look for places to improve the performance if necessary. Most of the time the "slow" way will be fast enough.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// don't do this</span>
<span class="hljs-keyword">var</span> ages: [<span class="hljs-type">Int</span>] = []
<span class="hljs-keyword">var</span> names: [<span class="hljs-type">String</span>] = []

<span class="hljs-keyword">for</span> user <span class="hljs-keyword">in</span> users {
    ages.append(user.age)
    names.append(user.name)
}

<span class="hljs-comment">// do this instead</span>
<span class="hljs-keyword">let</span> ages = users.<span class="hljs-built_in">map</span> { user <span class="hljs-keyword">in</span> user.age }
<span class="hljs-keyword">let</span> names = users.<span class="hljs-built_in">map</span> { user <span class="hljs-keyword">in</span> user.name }
</code></pre>
<p><strong>Prefer clarity over brevity.</strong> That is just a fancy way of saying that it is more important to write clear code than short code. Use as longer names for functions if it helps. Name anonymous closer arguments. Name a variable even if it is only used once. Break down large functions into smaller parts. Etc.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// both versions assume this extension</span>
<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">String</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">orIfEmpty</span><span class="hljs-params">(<span class="hljs-number">_</span> replacement: String)</span></span> -&gt; <span class="hljs-type">String</span> { isEmpty ? replacement : <span class="hljs-keyword">self</span> }
}

<span class="hljs-comment">// don't do this, real solution I found online by googling "Fizz Buzz solutions"</span>
(<span class="hljs-number">1</span>...<span class="hljs-number">15</span>).<span class="hljs-built_in">map</span> { (($<span class="hljs-number">0</span> % <span class="hljs-number">3</span> == <span class="hljs-number">0</span> ? <span class="hljs-string">"Fizz"</span> : <span class="hljs-string">""</span>) + ($<span class="hljs-number">0</span> % <span class="hljs-number">5</span> == <span class="hljs-number">0</span> ? <span class="hljs-string">"Buzz"</span> : <span class="hljs-string">""</span>)).orIfEmpty(<span class="hljs-string">"\($0)"</span>) }.forEach { <span class="hljs-built_in">print</span>($<span class="hljs-number">0</span>) }

<span class="hljs-comment">// do this instead</span>
<span class="hljs-comment">// name the function so that it is clear what it does at the call site</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">playFizzBuzz</span><span class="hljs-params">(from start: Int = <span class="hljs-number">1</span>, to end: Int)</span></span> {
    playFizzBuzz(start...end)
}

<span class="hljs-comment">// allow users to user whichever interface makes sense at call site</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">playFizzBuzz</span><span class="hljs-params">(<span class="hljs-number">_</span> range: ClosedRange&lt;Int&gt;)</span></span> {
    <span class="hljs-keyword">let</span> answers = range.<span class="hljs-built_in">map</span> { number <span class="hljs-keyword">in</span> <span class="hljs-comment">// name closure arguments</span>
        singleFizzBuzz(<span class="hljs-keyword">for</span>: number)
    }

    answers.forEach { answer <span class="hljs-keyword">in</span>
        <span class="hljs-built_in">print</span>(answer)
    }
}

<span class="hljs-comment">// pull out discrete pieces of logic to separate functions</span>
<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">singleFizzBuzz</span><span class="hljs-params">(<span class="hljs-keyword">for</span> number: Int)</span></span> -&gt; <span class="hljs-type">String</span> {
    <span class="hljs-comment">// use language-provided functions where you can</span>
    <span class="hljs-keyword">var</span> wordReplacement = number.isMultiple(of: <span class="hljs-number">3</span>) ? <span class="hljs-string">"Fizz"</span> : <span class="hljs-string">""</span>
    wordReplacement += number.isMultiple(of: <span class="hljs-number">5</span>) ? <span class="hljs-string">"Buzz"</span> : <span class="hljs-string">""</span>
    <span class="hljs-keyword">return</span> wordReplacement.orIfEmpty(<span class="hljs-string">"\(number)"</span>)
}

<span class="hljs-comment">// maybe a bit overkill for fizz buzz, but it illustrates the principle</span>
</code></pre>
<p><strong>Follow the conventions of the language and codebase.</strong> If you're an iOS developer writing Swift like I am, a good start is to read through <a target="_blank" href="https://www.swift.org/documentation/api-design-guidelines/">the API design guidelines</a> (where you'll notice some similarities with my advice here). If the code base you're working in has agreed-upon conventions, follow them! Even if it goes against your personal preference. Clarity is more important that your opinions. If you really care about something, get the team to agree upon a new convention and offer to migrate the existing code as much as you are able.</p>
<p>Finally, <strong>always be refactoring.</strong> If you don't know how, read <a target="_blank" href="https://martinfowler.com/books/refactoring.html">Martin Fowler's book on the topic</a>. (Even if you do know how, you should probably read it anyways.) Make a lot of small changes in the direction of cleaner/simpler/easier to understand code over time and it will add up. Any time you make changes to some part of the code stop and ask yourself this question: "What could I do in five minutes that would make it easier for the next person to understand?" Don't waste a ton of time on it, that is the opposite of what we're trying to do. But give it a couple minutes and you'll find something. Rename a variable. Break up the function. Add a signpost comment. Follow the boy scout rule. Always leave the code cleaner than you found it.</p>
<h2 id="heading-wrap-up">Wrap Up</h2>
<p>That's all I've got for today. Spend less time making your code short and clever and spend more time making it readable. Shift your mindset. Make lots of small changes to improve readability. They will compound into massive improvements over time.  And they will save you time, they will make your code easier to change, easier to debug and easier to communicate.</p>
<p>Let me know what you think. How do you go about making your code more readable? What are the cases where short and clever code is better? </p>
]]></content:encoded></item><item><title><![CDATA[Fixing An Unfixable Bug]]></title><description><![CDATA[If you have been an iOS developer for any length of time, you have certainly come across bugs that are hard to understand and hard to track down. And if you have an app out in the world that real human beings use you have probably encountered a bug o...]]></description><link>https://dilloncodes.com/fixing-an-unfixable-bug</link><guid isPermaLink="true">https://dilloncodes.com/fixing-an-unfixable-bug</guid><category><![CDATA[Swift]]></category><category><![CDATA[iOS]]></category><category><![CDATA[Xcode]]></category><dc:creator><![CDATA[Dillon McElhinney]]></dc:creator><pubDate>Mon, 07 Mar 2022 00:27:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/bmJAXAz6ads/upload/v1646612359312/Fo-PnPHT-.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you have been an iOS developer for any length of time, you have certainly come across bugs that are hard to understand and hard to track down. And if you have an app out in the world that real human beings use you have probably encountered a bug or crash that is happening for some subset of your users, but that you cannot for the life of you reproduce on your device. This is the story of one of those bugs. It was one where I went into it not having a clue what the problem was and where I fully expected to bang my head against the wall for a day without actually solving anything.</p>
<p><em>If you don't already know, my day job is working on an app called <a target="_blank" href="https://hallow.app">Hallow</a>. It is a prayer and meditation app (in the vein of Calm or Headspace) with Christian-themed content, but this is a crash that you could run into in practically any iOS app. And a lot of the process is relevant to finding/fixing bugs in any environment, not just iOS apps.</em></p>
<h2 id="heading-the-report">The Report</h2>
<p>A few weeks ago I was looking through our bug reports (we use <a target="_blank" href="https://www.bugsnag.com">bugsnag</a>) and I noticed a crash in our app. It was reported as having been introduce in the latest version of the app. It was listed as "unhandled". And every instance had a very short stacktrace which ended in a view that very few users ever see. The error message was <code>EXC_BAD_ACCESS</code>, with this detail: <code>Attempted to dereference garbage pointer 0x10.</code> The address of the garbage pointer was different in various instances (though many of them were <code>0x10</code> and <code>0x20</code>) but everything else was the same. </p>
<h2 id="heading-my-thoughts">My Thoughts</h2>
<p>Here are some of my initial thoughts on this crash, as I remember them now and from looking back through Slack messages to my coworkers.</p>
<ul>
<li>the view in the stacktrace was probably irrelevant. I'm not sure why it is showing up consistently, but there were way more of these crashes than users who ever get to that view. Probably a red herring</li>
<li>the crash was grouped in the reports by stacktrace, but this was a memory bug, having to do with a bad pointer. That meant a couple of things. There were probably other crashes with different stacktraces but with the same root cause. This bug may not have been introduced in the latest version, but bugsnag may just be unable to link the crashes in previous versions with the same cause</li>
<li><code>EXC_BAD_ACCESS</code> is not a particularly helpful error message, but knowing that we are trying to deference a garbage pointer gives me something to go off of. Instruments has a tool for profiling zombies which is helpful for tracking down exactly that sort bug.</li>
</ul>
<h2 id="heading-discovery">Discovery</h2>
<p>I confirmed the first two thoughts by doing a cursory glance of the other crashes in our logs. There were several other ones with the same <code>Attempted to dereference garbage pointer 0x10.</code> but with different stacktraces in the same version of the app. I took that to mean that the view bugsnag claimed was the issue was not actually the issue. There were also similar numbers of crashes in the last several versions of the app with that same message, which I took to mean that the version of the app that bugsnag claimed introduced it actually did not. That helped me broaden my scope as I looked through recent code changes. In retrospect, I should have dug back through versions and tried to find the version where this bug actually was introduced, but I didn't think of it at the time.</p>
<p>I also spent twenty minutes or so digging through the code looking into things that I thought <em>might</em> be the issue. In the grand scheme of things, this was not a particularly good use of time because I didn't really have enough information to go off of and I didn't find anything helpful. I should have gone straight to the zombie profiler.</p>
<h2 id="heading-profiling-the-app">Profiling The App</h2>
<p>I knew Instruments has a tool called "Zombies" which is used to track down over-released objects (which is what leads to garbage pointers), but I had only used it once several years ago, so I did some googling and found <a target="_blank" href="https://pratheeshbennet.medium.com/xcode-instruments-zombies-8b262b1ae9d8">this article about how to use it</a>.  I skimmed it and felt equipped enough to dive in and see what I could find. </p>
<p>Fortunately for me, it was incredibly easy. I started the app in Instruments, tapped on a piece of content and it immediately crashed and told me I had over-released an object. I clicked into the flag, as described in that article, and found that the object was a view controller representing a certain block of content in the app. I tried profiling several other times and found the same object every time, so I felt pretty confident that I knew where to dig in on the code.</p>
<h2 id="heading-the-culprit">The Culprit?</h2>
<p>The specifics of the bug in the case are not terribly important. I am trying to document the process I went through to find it and fix it more than trying to document this specific fix, but for those of you who are curious, it basically boiled down to this: </p>
<ul>
<li>We have a collection view cell that we use to wrap views.</li>
<li>We were sticking the aforementioned view controller's view into this cell in <code>collectionView(_:cellForItemAt:)</code></li>
<li>Nothing was holding on to that view controller, just a local reference. It was never added as a child view controller to any other view controller.</li>
</ul>
<p>The fix? Our first attempt was to just keep a reference to that view controller in the collection view cell, and remove it on reuse.</p>
<h2 id="heading-validation">Validation</h2>
<p>I re-ran the zombie profiler after implementing this fix and found that I could navigate all around the app without it crashing, which I took to mean that the issue had been resolved. But I knew that we wouldn't know if it really fixed the issue until we got it out to users and saw how it acted in the real world. So that's what we did. We merged the fix in for the next release and then closely monitored how it acted.</p>
<p>Here's what we saw:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646612069893/nYHYZhEje.png" alt="Screen Shot 2022-03-06 at 3.59.02 PM.png" /></p>
<p>As you can see, the stability for the version with the fix consistently stays above the stability for the version before it. It's not a huge percentage difference (because we have quite a few users and only a small number of them ever encountered this crash), but it got rid of a few hundred crashes per day, so it probably made a few people's day better, even if they didn't realize it. 🙌🏼</p>
<h2 id="heading-wrap-up">Wrap up</h2>
<p>So what can we learn from this experience? I have a couple of takeaways. First, <strong>take a breath</strong>. It always pays to spend some time thinking through, reasoning about, and talking out bugs that you don't understand. If I had just taken the stack trace and tried to dig into that specific view I would have wasted a bunch of time. Second, it is important to <strong>do your homework thoroughly in cases where there is not a clear line between the crash report and the bug causing it</strong>. If I had spent a little more time investigating crash reports, I may have been able to narrow down the version that introduced the bug, which may have made the fix more obvious to me. Third, <strong>get familiar with the tools available</strong>. Most iOS developers I know are pretty comfortable with <code>print</code> statements and breakpoints, but way fewer of them are comfortable with Instruments (or have ever even opened it). It probably doesn't make sense to become an expert in of all the various tools before you actually need them, but you should at least learn about what is available to you so when the time comes that you need one, you'll know that it exists. <a target="_blank" href="https://www.raywenderlich.com/16126261-instruments-tutorial-with-swift-getting-started">This should get you started.</a></p>
<p>And finally, <strong>sometimes you can actually fix bugs that are beyond your ability</strong>! I know it can feel hopeless when you're looking at a cryptic crash log and trying to reason about something that is theoretical. And sometimes you won't find the answer. But if you stick with it, keep trying, and seek out the help you need, you can actually fix some of these bugs and grow as a developer in the process. When it happens, it feels amazing, but even when it doesn't you can still learn from the experience.</p>
<p>Let me know about a time you tried to tackle a bug that you felt was too hard for you. Did you find the fix? What did you learn?</p>
]]></content:encoded></item><item><title><![CDATA[On Imposter Syndrome]]></title><description><![CDATA[A friend of mine was recently hired as a "senior" engineer. He's a sharp guy. He went through a bootcamp. He had worked at a company for a couple years as a junior, gaining experience and learning along the way. And now a new company was offering him...]]></description><link>https://dilloncodes.com/on-imposter-syndrome</link><guid isPermaLink="true">https://dilloncodes.com/on-imposter-syndrome</guid><category><![CDATA[iOS]]></category><category><![CDATA[Developer]]></category><category><![CDATA[Career]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[Dillon McElhinney]]></dc:creator><pubDate>Sat, 21 Aug 2021 19:08:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1629572821673/naYEN8xnB.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A friend of mine was recently hired as a "senior" engineer. He's a sharp guy. He went through a bootcamp. He had worked at a company for a couple years as a junior, gaining experience and learning along the way. And now a new company was offering him a job with a bump up in title (and presumably pay). He had accepted the job and as his first day approached, the feelings of imposter syndrome started to creep in. Would he be able to keep up with people around him? Would he be able to live up to the title? What if they "found him out" and called him what he felt like, an imposter?</p>
<p>Then he did something you should do when you have those feelings. He reached out to people who knew him, people he trusted, and people who had been through similar situations. He asked for advice. Advice on what do to about imposter syndrome and advice on how to spin up at a new job quickly. I happened to be one of the people he asked and so I wrote him a message with my thoughts. It seemed to help him, so I decided to spruce it up a little bit and give the same advice to you.</p>
<p>This advice assumes that you have had at least one job where you felt like you knew what you were doing when you left it. It also assumes that you objectively have the technical and soft skills necessary to do the job that you're hired for. That is, it assumes that you are not actually an imposter. (Don't try to trick people into thinking that you know more than you do to get a better job title. It won't work for very long.) And, it assumes that your new job is a good and healthy work environment. There are places where this advice will not work and my advice to you then would be to find a new place to work. With all that said, let's go.</p>
<h2 id="it-will-feel-like-you-dont-know-enough">It Will Feel Like You Don't Know Enough</h2>
<p>The first point is that it will almost certainly feel like you know less than you did at your previous job. Because you do. You probably haven't lost any of your technical skills (you may even be sharper after brushing up for interviews and whatnot), but what you don't have now is familiarity. And unfortunately for you, familiarity is going to be strongly correlated with velocity. This can compound in a couple of way that leave you feeling useless.</p>
<p><strong>You have switched from a context where you had a lot of familiarity to one where you have very little.</strong> And because you are a human with recency bias, you will compare your velocity now with your velocity when you left your last job. But really, you should compare it with your velocity when you <em>started</em> your last job. Did it take you two weeks to merge your first PR at the last job? If it takes you one week this time, you've cut that ramp up time in half. Regardless of actual metrics, know that it will take you less time to reach the same level of familiarity at the new company as it did at your last company because you have a whole set of experience under your belt now that you didn't then. <em>Disclaimer: You shouldn't only look at ramp up time because this is also largely dependent on the quality of your new employer's documentation and onboarding process.</em></p>
<p><strong>You have switched from a context where you may have been one of the people with the most familiarity to one where you likely have the least.</strong> The people around you at the new job will be familiar with the processes/patterns/technologies/code/etc in ways that you are not (yet). It will feel like everyone you work with is flying and you're trying to keep up on a unicycle. That's (probably) not because they are better developers than you, it is largely because they have been here longer and are more familiar with the requirements at this particular job. You will get there in time too.</p>
<p>Regardless of how you feel, try to remind yourself that they wouldn't have hired you as a senior if they didn't think you were qualified for that title. No one knows everything. And you probably know a lot more than you feel like you do. I can tell you from experience that if you make any effort at all to continue learning and growing as an engineer after you get a job, you are already on trajectory to reach the upper echelon of engineers. </p>
<h2 id="how-to-spin-up-like-a-senior">How to Spin Up Like A Senior</h2>
<p><strong>Write down anything that doesn't make sense to you.</strong> Anything you get stuck on. Any questions you have (this includes questions about how the business works and why things are the way they are). Then you can just fire them off one after another whenever you meet with your onboarding buddy or manager or whoever, and you'll look really prepared. No one expects that you will have a high velocity right when you first start, but they will be impressed if you are actively participating in the process and pushing forward. Bonus points if you use those questions/answers to improve their existing documentation for the next person who has to onboard.</p>
<p><strong>Look for quality of life improvements that you can make for other people.</strong> Tackle tech debt and refactors where you can because they are helpful for the team and they will help you get familiar with the codebase/patterns. I'm not saying take on every bit of menial work that exists, but everywhere I've ever worked has had at least a couple of things that they have been meaning to get to and no one has time for. You typically don't have too many responsibilities right when you first start, so use that time to knock a couple of things off the list. Refactor those last few legacy classes to the current pattern, fix the dark mode or accessibility bug, or whatever it is that needs to be done.</p>
<p><strong>Stick to the style guide.</strong> Code style is not the place to differentiate yourself when you start. If there is a style guide at your new company, it probably took a while to get to and there is probably at least one person with very strong opinions about it. Do your best to stick to it when you start and if there is something that you have strong opinions about, you'll have a much better idea of when/where to bring it up in three to six months. If there isn't an existing style guide, see if you can head up the process to establish one and get a linter in place. </p>
<p><strong>Meet as many people as you can (especially if you're remote).</strong> Ask lots of questions about them, their role at the company and how the business works. It has been incredibly helpful in my own career to figure out who does what (meaning, who I should ask about different kinds of things) and what the levers of the business are so you can understand why decisions are made and when to speak up with ideas if you have them. On top of that, people like to work with people that they like. Be personable, develop your soft skills and make some friends.</p>
<p><strong>Starting a new job is basically the same as getting over a cold.</strong> Prepare to feel out of sorts for a few weeks (or months), but know that you'll get over it eventually. Get lots of sleep and drink lots of fluids. Work hard and do your best, but make sure you establish a good work/life balance from the start. </p>
<h2 id="wrap-up">Wrap Up</h2>
<p>So there you go, that is my advice as it stands today. Have confidence in yourself, know that your familiarity (and velocity) will grow over time, and there are things you can do that both provide value and help you accelerate growing that familiarity. How about you? What has your experience been? What advice would you give a friend to help them get over imposter syndrome or spin up at a new job?</p>
<hr />
<p>Header photo by <a href="https://unsplash.com/@martenbjork">Marten Bjork</a> on <a href="https://unsplash.com/s/photos/new-job">Unsplash.</a></p>
]]></content:encoded></item><item><title><![CDATA[A Look At iOS Native Half Sheets]]></title><description><![CDATA[One of the things that stood out to me from WWDC this year was the ability to have native half sheets. This has been a request from the designers at every job I have ever had and until now we have always had to roll our own. So I thought I would play...]]></description><link>https://dilloncodes.com/native-ios-half-sheets</link><guid isPermaLink="true">https://dilloncodes.com/native-ios-half-sheets</guid><category><![CDATA[iOS]]></category><category><![CDATA[Swift]]></category><category><![CDATA[Xcode]]></category><category><![CDATA[UX]]></category><category><![CDATA[UI Design]]></category><dc:creator><![CDATA[Dillon McElhinney]]></dc:creator><pubDate>Sat, 19 Jun 2021 00:43:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1624063334697/5zg_sT8Q3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>One of the things that stood out to me from WWDC this year was the ability to have native half sheets. This has been a request from the designers at every job I have ever had and until now we have always had to roll our own. So I thought I would play around with the code from <a target="_blank" href="https://developer.apple.com/wwdc21/10063">this video</a> and see how they work in practice. This code is all written in Xcode 13, beta 1 on macOS 11.3 (Big Sur) and run on iOS 15 simulators. </p>
<p>And please excuse the quality of the gifs. Gradients don't play super well with a limited palette, but it was the easiest way I could find to have some simple examples of what the movement of these sheets looks like.</p>
<h2 id="finding-the-sheet-presentation-controller">Finding The Sheet Presentation Controller</h2>
<p>The first thing I noticed is that the sample code doesn't actually compile (at least not on the version of Xcode I'm running). <code>UIViewController</code> doesn't have a property called <code>sheetPresentationController</code>. It is accessible through the <code>presentationController</code> property, assuming the <code>modalPresentationStyle</code> is set to <code>pageSheet</code> or <code>formSheet</code>, but you have to cast it as a <code>UISheetPresentationController</code> to access the new properties. I added this shim for now to get the sample code to run, but I assume the new <code>sheetPresentationController</code> property will be made available eventually.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">UIViewController</span> </span>{
 <span class="hljs-keyword">var</span> sheetPresentationController: <span class="hljs-type">UISheetPresentationController?</span> {
   presentationController <span class="hljs-keyword">as</span>? <span class="hljs-type">UISheetPresentationController</span>
 }
}
</code></pre>
<p>With that available, I had a route to start adding some sheets:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">viewDidLoad</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">super</span>.viewDidLoad()

    view.backgroundColor = .systemTeal
    presentSheet()
}

<span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">viewDidAppear</span><span class="hljs-params">(<span class="hljs-number">_</span> animated: Bool)</span></span> {
    presentSheet()
}

<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">presentSheet</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">let</span> viewController = <span class="hljs-type">UIViewController</span>()
    viewController.view.backgroundColor = .systemOrange

    <span class="hljs-keyword">let</span> sheet = viewController.sheetPresentationController
    sheet?.detents = [.medium(), .large()]

    present(viewController, animated: <span class="hljs-literal">true</span>)
}
</code></pre>
<p>That gives us a bottom sheet like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624061865764/yHbJIHHm0.gif" alt="first-half-sheet.gif" /></p>
<h2 id="making-it-look-nice">Making It Look Nice</h2>
<p>It's pretty cool out of the box. It looks good, you can expand it up to full height and back and dismiss it like you would expect. But I wanted to see how customizable it is, see what it is good for and what it isn't, etc. And I want it to look good in the process, so let's get some gradients in there.</p>
<p>First, I'm using this simple <code>GradientView</code> which is a <code>UIView</code> subclass that has a <code>CAGradientLayer</code> as its layer class and provides direct access to it.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GradientView</span>: <span class="hljs-title">UIView</span> </span>{
    <span class="hljs-keyword">override</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">var</span> <span class="hljs-title">layerClass</span>: <span class="hljs-title">AnyClass</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-type">CAGradientLayer</span>.<span class="hljs-keyword">self</span>
    }

    <span class="hljs-keyword">var</span> gradientLayer: <span class="hljs-type">CAGradientLayer</span> {
        <span class="hljs-keyword">return</span> layer <span class="hljs-keyword">as</span>! <span class="hljs-type">CAGradientLayer</span>
    }
}
</code></pre>
<p>Then, I set the <code>view</code> of <code>ViewController</code> to be that <code>GradientView</code></p>
<pre><code class="lang-swift"><span class="hljs-keyword">private</span> <span class="hljs-built_in">lazy</span> <span class="hljs-keyword">var</span> contentView: <span class="hljs-type">GradientView</span> = .<span class="hljs-keyword">init</span>()

<span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">loadView</span><span class="hljs-params">()</span></span> {
    view = contentView
}
</code></pre>
<p>And then make some semi-random combinations of colors:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> colorSets: [[<span class="hljs-type">UIColor</span>]] = [
    [.systemTeal, .systemGreen, .systemOrange],
    [.systemRed, .systemPurple, .systemBlue],
    [.systemPink, .systemIndigo, .systemTeal],
    [.systemPink, .systemYellow, .systemGreen],
    [.systemGreen, .systemTeal, .systemPurple],
    [.systemYellow, .systemPink, .systemIndigo],
    [.systemRed, .systemYellow, .systemTeal]
]

<span class="hljs-comment">// in viewDidLoad()</span>
contentView.gradientLayer.colors = colorSets.randomElement()!
                                            .<span class="hljs-built_in">map</span>(\.cgColor)
</code></pre>
<p>To add a little more variety, I also added a couple of different start points:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> startPoints: [<span class="hljs-type">CGPoint</span>] = [
    <span class="hljs-type">CGPoint</span>(x: <span class="hljs-number">0.5</span>, y: <span class="hljs-number">0</span>),
    <span class="hljs-type">CGPoint</span>(x: <span class="hljs-number">0</span>, y: <span class="hljs-number">0.3</span>),
    <span class="hljs-type">CGPoint</span>(x: <span class="hljs-number">0.5</span>, y: <span class="hljs-number">1</span>),
    <span class="hljs-type">CGPoint</span>(x: <span class="hljs-number">0</span>, y: <span class="hljs-number">0.8</span>),
    <span class="hljs-type">CGPoint</span>(x: <span class="hljs-number">1</span>, y: <span class="hljs-number">0.3</span>),
    <span class="hljs-type">CGPoint</span>(x: <span class="hljs-number">1</span>, y: <span class="hljs-number">0.8</span>),
]

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">CGPoint</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">opposite</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">CGPoint</span> {
        <span class="hljs-keyword">let</span> x = <span class="hljs-number">1</span> - <span class="hljs-keyword">self</span>.x
        <span class="hljs-keyword">let</span> y = <span class="hljs-number">1</span> - <span class="hljs-keyword">self</span>.y
        <span class="hljs-keyword">return</span> <span class="hljs-type">CGPoint</span>(x: x, y: y)
    }
}

<span class="hljs-comment">// in viewDidLoad()</span>
<span class="hljs-keyword">let</span> start = startPoints.randomElement()!
contentView.gradientLayer.startPoint = start
contentView.gradientLayer.endPoint = start.opposite()
</code></pre>
<p>FInally, to add new sheets, I added a button instead of doing it automatically in <code>viewDidAppear</code>. This allows it to be recursive, so you can just keep adding new iterations onto the stack. This is using the new <code>UIButtonConfiguration</code>, which I won't go into detail on here, but maybe in a future article. Needless to say, it is also pretty nice.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> addButton = <span class="hljs-type">UIButton</span>(configuration: .plain())

<span class="hljs-comment">// in viewDidLoad()</span>
<span class="hljs-keyword">let</span> arrowConfig = <span class="hljs-type">UIImage</span>.<span class="hljs-type">SymbolConfiguration</span>(pointSize: <span class="hljs-number">36</span>,
                                              weight: .black)
<span class="hljs-keyword">let</span> arrow = <span class="hljs-type">UIImage</span>(systemName: <span class="hljs-string">"arrow.clockwise"</span>,
                    withConfiguration: arrowConfig)
addButton.configuration?.image = arrow
addButton.tintColor = .white
<span class="hljs-keyword">let</span> addAction = <span class="hljs-type">UIAction</span> { <span class="hljs-number">_</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">self</span>.presentSheet() }
addButton.addAction(addAction, <span class="hljs-keyword">for</span>: .primaryActionTriggered)

view.addSubview(addButton)
addButton.centerAnchors == view.centerAnchors

<span class="hljs-comment">// in presentSheet()</span>
sheetPresentationController?.animateChanges {
    sheetPresentationController?.selectedDetentIdentifier = .large
}

<span class="hljs-keyword">let</span> viewController = <span class="hljs-type">ViewController</span>()
</code></pre>
<p>That leads to something that looks like this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624061899847/QpiVehkhK.gif" alt="gradient-sheets.gif" /></p>
<h2 id="customizing-things">Customizing Things</h2>
<p>Now that's looking a lot more fun! And it gives us a pretty solid foundation to mess with some of the interface talked about in the WWDC session. The first thing I tried as messing with the <code>preferredCornerRadius</code>, which I found is not yet a property on <code>UISheetPresentationController</code>. But there is a private version called <code>__preferredCornerRadius</code>, which, again, I'm assuming will be fixed before the release of the GM. To mess around with this, I added a slider, that will let us set it in the app.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">static</span> <span class="hljs-keyword">var</span> preferredCornerRadius: <span class="hljs-type">CGFloat</span> = <span class="hljs-number">24</span>
<span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> cornerRadiusSlider = <span class="hljs-type">UISlider</span>()

<span class="hljs-comment">// in viewDidLoad()</span>
cornerRadiusSlider.maximumValue = <span class="hljs-number">60</span>
cornerRadiusSlider.minimumValue = <span class="hljs-number">0</span>
cornerRadiusSlider.maximumTrackTintColor = .white.withAlphaComponent(<span class="hljs-number">0.3</span>)
cornerRadiusSlider.minimumTrackTintColor = .white
cornerRadiusSlider.value = <span class="hljs-type">Float</span>(<span class="hljs-type">Self</span>.preferredCornerRadius)

<span class="hljs-keyword">let</span> updateRadiusAction = <span class="hljs-type">UIAction</span> { <span class="hljs-number">_</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">self</span>.updateRadius() }
cornerRadiusSlider.addAction(updateRadiusAction, <span class="hljs-keyword">for</span>: .primaryActionTriggered)

view.addSubview(cornerRadiusSlider)
cornerRadiusSlider.horizontalAnchors == view.readableContentGuide.horizontalAnchors + <span class="hljs-number">20</span>
cornerRadiusSlider.topAnchor == addButton.bottomAnchor + <span class="hljs-number">20</span>

<span class="hljs-comment">// in presentSheet()</span>
sheet?.__preferredCornerRadius = <span class="hljs-type">Self</span>.preferredCornerRadius

<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">updateRadius</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">let</span> radius = <span class="hljs-type">CGFloat</span>(cornerRadiusSlider.value)
    sheetPresentationController?.__preferredCornerRadius = radius
    <span class="hljs-type">Self</span>.preferredCornerRadius = radius
}
</code></pre>
<p>This works pretty much as you would expect. As you move the slider, the corner radius updates.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624061943459/OM6FScgqU.gif" alt="changing-corner-radius.gif" /></p>
<p>Interestingly, setting the preferred corner radius on the sheet presenter doesn't affect the view behind if it is at half-height, but it does at full height.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624061965544/DHwDPM5yJ.gif" alt="changing-radius-at-half-height.gif" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624061979660/Xku-Bbxhn.gif" alt="changing-radius-at-full-height.gif" /></p>
<p>Even more interesting, it <em>only</em> affects the view immediately behind it, even at full height.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624061990460/v8YVZo0a9.gif" alt="only-changing-the-view-behind.gif" /></p>
<p>Most of these things shouldn't really affect much in practice, because how often are you presenting sheet on top of sheet on top of sheet, or allowing the user to change the corner radius of a view at will? But it is good to know where it might fall down.</p>
<p><code>prefersGrabberVisible</code>, <code>prefersEdgeAttachedInCompactHeight</code>, and <code>smallestUndimmedDetentIdentifier</code> are all accessible and work expected. I added these methods to expose them in the app, and hooked them up to respective buttons.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">updateGrabber</span><span class="hljs-params">()</span></span> {
    showsGrabberButton.isSelected.toggle()
    sheetPresentationController?.animateChanges {
        sheetPresentationController?.prefersGrabberVisible = showsGrabberButton.isSelected
    }
}

<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">updateEdgeAttached</span><span class="hljs-params">()</span></span> {
    edgeAttachedButton.isSelected.toggle()
    sheetPresentationController?.animateChanges {
        sheetPresentationController?.prefersEdgeAttachedInCompactHeight = edgeAttachedButton.isSelected
    }
}

<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">updateDimming</span><span class="hljs-params">(<span class="hljs-number">_</span> detent: Detent)</span></span> {
    sheetPresentationController?.animateChanges {
        sheetPresentationController?.smallestUndimmedDetentIdentifier = detent.identifier
    }
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624062041326/224CKgQFc.png" alt="sheet-with-grabber.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624062058241/QG7z27-MI.png" alt="sheet-in-landscape.png" /></p>
<p>And it all works well on iPad too, although you have to access the sheet controller through <code>popoverPresentationController?.adaptiveSheetPresentationController</code> if your view is presented in a popover.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624062280533/7wNraWldu.gif" alt="testing-on-ipad.gif" /></p>
<p>One thing that is really cool is that, if you set the sheet to not dim the view behind it, you can still interact with the view controller behind it. That doesn't mean much in this toy app, but the WWDC video had a good example of showing an image picker on the bottom (in the front view controller) and displaying the image picked on the top (in the back view controller).</p>
<h2 id="wrap-up">Wrap Up</h2>
<p>All in all, I think it looks pretty slick and (mostly) works like you would expect it to out of the box. If they get the last few pieces of the interface cleaned up before the GM release, I'll definitely be using it my apps when the design calls for it. Download my toy app and play around with this stuff for yourself in <a target="_blank" href="https://github.com/dillon-mce/SheetTest">this repo.</a></p>
]]></content:encoded></item><item><title><![CDATA[Building Airbnb's UI From Scratch - Part 6]]></title><description><![CDATA[In this series of posts I am going to be building out the UI for Airbnb's "Explore" tab from scratch. This is an exercise in figuring out how to achieve the different layouts and effects you find in popular apps and I chose Airbnb because I thought i...]]></description><link>https://dilloncodes.com/airbnb-part-6</link><guid isPermaLink="true">https://dilloncodes.com/airbnb-part-6</guid><category><![CDATA[iOS]]></category><category><![CDATA[Xcode]]></category><category><![CDATA[Swift]]></category><category><![CDATA[UI]]></category><dc:creator><![CDATA[Dillon McElhinney]]></dc:creator><pubDate>Sun, 06 Jun 2021 01:49:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1618036941646/afjwJBtBD.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this series of posts I am going to be building out the UI for Airbnb's "Explore" tab from scratch. This is an exercise in figuring out how to achieve the different layouts and effects you find in popular apps and I chose <a target="_blank" href="https://apps.apple.com/us/app/airbnb/id401626263?uo=4">Airbnb</a> because I thought it had several great examples. It is not intended to be used for anything other than for educational purposes and all my code will be available at <a target="_blank" href="https://github.com/dillon-mce/Airbnb-Home">this repo</a> if you want to follow along and build it yourself. A few disclaimers: </p>
<ul>
<li>For all the illustrations in the app I just took screenshots of the actual app and cropped them to size. They are not the full versions of the illustrations and they are not really even formatted correctly for use in an iOS app. I tried to spend as little time as possible on that prep. </li>
<li>We will get the fonts, colors and icons as close as we can with just using the system versions. I am pretty sure the font and icons that Airbnb actually uses would require a license and I don't care that much about making this exact. With the way we organize it, it would be pretty easy to swap in the real ones if you want to take that step yourself.</li>
<li>We will not do any business logic. We will hard code all the data and not handle any user actions. Again, this is not meant to be production code, just an exploration of some UI techniques.</li>
<li>There is a good chance that Airbnb will look different by the time you see this. Honestly, there's a non-zero chance that it will change before I finish writing this, so the live app will probably look different than what you see me build, but the layout/principles should be pretty much the same. <em>(Editorial note: it has already changed before I was able to finish writing this series, but I have a few screenshots of what the app looked like before, and we'll just build up to the spec that I created.)</em></li>
</ul>
<p>Here's what our final product will look like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1618017103718/39dgJOE5X.gif" alt="Design Spec.gif" /></p>
<p>With all that said, let's go.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/18quVKZksGI">https://youtu.be/18quVKZksGI</a></div>
<h1 id="animating-the-header">Animating The Header</h1>
<p>Now that we have the collection view built out, the only thing left to do is deal with the header. Let's start by laying that out. It basically consists of a "card" with a background image, and search bar which has its own little container, a big title label with a button and a separator at the bottom to separate it from the collection view.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HeaderView.swift</span>

<span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> card = <span class="hljs-type">UIView</span>()
<span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> imageView = <span class="hljs-type">UIImageView</span>()
<span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> searchContainer = <span class="hljs-type">UIView</span>()
<span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> searchBar = <span class="hljs-type">UIButton</span>(type: .roundedRect)
<span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> titleLabel = <span class="hljs-type">UITextView</span>()
<span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> button = <span class="hljs-type">UIButton</span>(type: .roundedRect)
<span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> separator = <span class="hljs-type">UIView</span>()
</code></pre>
<p>A couple of things to call out. The "search bar" is just a button. I'm doing that primarily because it is what the Airbnb app is doing. If you go look in the app you'll find if you tap on the search bar it is just a button that navigates you to a "search view". And the title label is actually a text view. I went with that because it was the easiest way I could find to get the line height to look the way it is in the Airbnb app. You'll see why in a moment.</p>
<p>Next, we need to do some updates so these things all look like they should:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HeaderView.swift</span>

<span class="hljs-comment">// in configure()</span>
backgroundColor = .black

card.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
card.layer.cornerRadius = <span class="hljs-number">20</span>
card.layer.masksToBounds = <span class="hljs-literal">true</span>

imageView.contentMode = .scaleAspectFill
imageView.image = <span class="hljs-type">UIImage</span>(named: <span class="hljs-string">"background"</span>)

searchContainer.backgroundColor = .systemBackground
searchContainer.setBackgroundAlpha(<span class="hljs-number">0</span>)

searchBar.backgroundColor = .secondarySystemBackground
searchBar.layer.cornerRadius = <span class="hljs-number">22</span>
searchBar.setTitle(<span class="hljs-string">"Where are you going?"</span>, <span class="hljs-keyword">for</span>: .normal)
searchBar.setTitleColor(.label, <span class="hljs-keyword">for</span>: .normal)
searchBar.setImage(<span class="hljs-type">UIImage</span>(systemName: <span class="hljs-string">"magnifyingglass"</span>), <span class="hljs-keyword">for</span>: .normal)
searchBar.tintColor = .systemPink
searchBar.titleLabel?.font = .custom(style: .button)
searchBar.imageView?.contentMode = .scaleAspectFit
searchBar.imageEdgeInsets = .<span class="hljs-keyword">init</span>(top: <span class="hljs-number">14</span>, <span class="hljs-keyword">left</span>: <span class="hljs-number">0</span>, bottom: <span class="hljs-number">14</span>, <span class="hljs-keyword">right</span>: <span class="hljs-number">4</span>)

titleLabel.text = <span class="hljs-string">"Go\nNear"</span>
titleLabel.textColor = .white
titleLabel.font = .custom(style: .largeTitle)
titleLabel.setLineHeightMultiple(to: <span class="hljs-number">0.7</span>)
titleLabel.isScrollEnabled = <span class="hljs-literal">false</span>
titleLabel.isEditable = <span class="hljs-literal">false</span>
titleLabel.isSelectable = <span class="hljs-literal">false</span>
titleLabel.backgroundColor = .clear

button.backgroundColor = .secondarySystemBackground
button.layer.cornerRadius = <span class="hljs-number">6</span>
button.setTitle(<span class="hljs-string">"Explore nearby stays"</span>, <span class="hljs-keyword">for</span>: .normal)
button.setTitleColor(.label, <span class="hljs-keyword">for</span>: .normal)
button.titleLabel?.font = .custom(style: .button)
button.contentEdgeInsets = .<span class="hljs-keyword">init</span>(top: <span class="hljs-number">8</span>, <span class="hljs-keyword">left</span>: <span class="hljs-number">8</span>, bottom: <span class="hljs-number">8</span>, <span class="hljs-keyword">right</span>: <span class="hljs-number">8</span>)

separator.backgroundColor = .quaternaryLabel

<span class="hljs-comment">// in constrain()</span>
addSubviews(card, titleLabel, button, separator)
card.addSubviews(imageView, searchContainer)
searchContainer.addSubview(searchBar)

heightAnchor == <span class="hljs-number">600</span>

card.topAnchor == <span class="hljs-number">40</span>
card.horizontalAnchors == horizontalAnchors
card.bottomAnchor == bottomAnchor

imageView.edgeAnchors == card.edgeAnchors

searchContainer.topAnchor == card.topAnchor
searchContainer.horizontalAnchors == card.horizontalAnchors
searchContainer.heightAnchor &gt;= <span class="hljs-number">60</span>

searchBar.heightAnchor == <span class="hljs-number">44</span>
searchBar.topAnchor &gt;= searchContainer.topAnchor + <span class="hljs-number">24</span>
searchBar.topAnchor &gt;= safeAreaLayoutGuide.topAnchor + <span class="hljs-number">12</span>
searchBar.horizontalAnchors == searchContainer.horizontalAnchors + <span class="hljs-number">24</span>
searchBar.bottomAnchor == searchContainer.bottomAnchor - <span class="hljs-number">12</span>

titleLabel.topAnchor == safeAreaLayoutGuide.topAnchor + <span class="hljs-number">160</span>
titleLabel.leadingAnchor == leadingAnchor + <span class="hljs-number">24</span>

button.topAnchor == titleLabel.bottomAnchor
button.leadingAnchor == titleLabel.leadingAnchor

separator.heightAnchor == <span class="hljs-number">1</span>
separator.horizontalAnchors == horizontalAnchors
separator.bottomAnchor == bottomAnchor
</code></pre>
<p>You'll notice that we're using a couple of new fonts here, and a few new helper methods that we haven't written yet. Let's add the button font and the large title font:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Extensions/UIFont+Custom.swift</span>

<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">var</span> largeTitle: <span class="hljs-type">UIFont</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-type">UIFontMetrics</span>(forTextStyle: .largeTitle)
        .scaledFont(<span class="hljs-keyword">for</span>: .systemFont(ofSize: <span class="hljs-number">64</span>, weight: .heavy))
}

<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">var</span> button: <span class="hljs-type">UIFont</span> {
    <span class="hljs-type">UIFontMetrics</span>(forTextStyle: .headline)
        .scaledFont(<span class="hljs-keyword">for</span>: .systemFont(ofSize: <span class="hljs-number">13</span>, weight: .semibold))
}

<span class="hljs-comment">// in Style</span>
<span class="hljs-keyword">case</span> largeTitle
<span class="hljs-keyword">case</span> button
<span class="hljs-comment">// other cases...</span>

<span class="hljs-comment">// in custom(style:)</span>
<span class="hljs-keyword">case</span> .largeTitle: <span class="hljs-keyword">return</span> largeTitle
<span class="hljs-keyword">case</span> .button: <span class="hljs-keyword">return</span> button
</code></pre>
<p>Then we'll add a helper on <code>UIView</code> that will just let us adjust the alpha component of the background a little more concisely.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Extensions/UIView+Helpers.swift</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">setBackgroundAlpha</span><span class="hljs-params">(<span class="hljs-number">_</span> alpha: CGFloat)</span></span> {
    backgroundColor = backgroundColor?.withAlphaComponent(alpha)
}
</code></pre>
<p>Finally, we'll add a helper on <code>UITextView</code> which will allow us to set the line height multiple to match the design.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Extensions/UITextView+Helpers.swift</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">setLineHeightMultiple</span><span class="hljs-params">(to lineHeightMultiple: CGFloat = <span class="hljs-number">0.0</span>)</span></span> {

    <span class="hljs-keyword">let</span> paragraphStyle = <span class="hljs-type">NSMutableParagraphStyle</span>()
    paragraphStyle.lineHeightMultiple = lineHeightMultiple

    <span class="hljs-keyword">let</span> attributedString: <span class="hljs-type">NSMutableAttributedString</span>
    <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> attributedText = attributedText {
        attributedString = <span class="hljs-type">NSMutableAttributedString</span>(attributedString: attributedText)
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> text = text {
        attributedString = <span class="hljs-type">NSMutableAttributedString</span>(string: text)
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span>
    }

    attributedString.addAttribute(.paragraphStyle,
                                  value:paragraphStyle,
                                  range:<span class="hljs-type">NSMakeRange</span>(<span class="hljs-number">0</span>, attributedString.length))

    attributedText = attributedString
}
</code></pre>
<p>If you run the app right now you'll see that it looks pretty close to the spec.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1617937796660/_Fao3f7ao.png" alt="Screen Shot 2021-03-08 at 8.40.59 AM.png" /></p>
<p>One difference is that the space above the card is not quite right. That is because we're not accounting for the safe area yet. Let's do that now.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HeaderView.swift</span>

<span class="hljs-comment">// add a properties for holding the height and a reference to the constraint</span>
<span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> maxTopSpace: <span class="hljs-type">CGFloat</span> = <span class="hljs-number">40</span>
<span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> topSpaceConstraint = <span class="hljs-type">NSLayoutConstraint</span>()

<span class="hljs-comment">// capture the reference in constrain()</span>
topSpaceConstraint = card.topAnchor == topAnchor + maxTopSpace

<span class="hljs-comment">// update the height and constraint whenever the safe area insets change</span>
<span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">safeAreaInsetsDidChange</span><span class="hljs-params">()</span></span> {
    maxTopSpace = <span class="hljs-number">40</span> + safeAreaInsets.top
    topSpaceConstraint.constant = maxTopSpace
}
</code></pre>
<p>Another difference is that the status bar isn't the right color. We'll need to fix that in <code>HomeViewController</code>:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HomeViewController.swift</span>

<span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> statusBarStyle: <span class="hljs-type">UIStatusBarStyle</span> = .lightContent

<span class="hljs-keyword">override</span> <span class="hljs-keyword">var</span> preferredStatusBarStyle: <span class="hljs-type">UIStatusBarStyle</span> {
    statusBarStyle
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1617938069782/MJu8lsueL.png" alt="Screen Shot 2021-04-08 at 7.13.06 PM.png" /></p>
<h2 id="making-it-collapsible">Making It Collapsible</h2>
<p>Now it looks like it should, but if you scroll the collection view, you'll find that it is stuck at the size it is. Let's fix that.</p>
<p>We're going to add a method called <code>updateHeader</code> which will be what other views will call when they want the header to change size. It will take the old Y offset and the new Y offset and it will return a <code>CGFloat</code> which reflects an updated Y offset. We need to return the updated offset so the caller (the scrollview) can update its offset as its size changes. If you don't do this the scroll will look a little "bouncy" under certain circumstances. I know that is a lot, but it will all fall into place as we put the pieces together. For now, it looks like this:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HeaderView.swift</span>

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">updateHeader</span><span class="hljs-params">(newY: CGFloat, oldY: CGFloat)</span></span> -&gt; <span class="hljs-type">CGFloat</span> {
        <span class="hljs-comment">// do calculations and update view</span>

        <span class="hljs-keyword">return</span> newY
    }
</code></pre>
<p>Now we need to call that method, which we're going to do in <code>HomeView</code> in a <code>UIScrollViewDelegate</code> method called <code>scrollViewDidScroll(_:)</code></p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HomeView.swift</span>

<span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> oldYOffset: <span class="hljs-type">CGFloat</span> = <span class="hljs-number">0</span>

<span class="hljs-comment">// in configure()</span>
collectionView.delegate = <span class="hljs-keyword">self</span>

<span class="hljs-comment">// at bottom of file</span>
<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">HomeView</span>: <span class="hljs-title">UICollectionViewDelegate</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">scrollViewDidScroll</span><span class="hljs-params">(<span class="hljs-number">_</span> scrollView: UIScrollView)</span></span> {
        <span class="hljs-keyword">let</span> yOffset = scrollView.contentOffset.y

        <span class="hljs-keyword">let</span> updatedY = headerView.updateHeader(newY: yOffset, oldY: oldYOffset)
        scrollView.contentOffset.y = updatedY

        oldYOffset = scrollView.contentOffset.y
    }
}
</code></pre>
<p>You can see that we are grabbing the current Y offset, passing it and the old one to <code>updateHeader</code> and using the returned <code>CGFloat</code> to update the scroll view's offset. Finally, we update the old offset to be the current one. Right now you can run the app and you'll find that it runs but doesn't do anything different. Which is what we'd expect. So far, we've just added some unnecessary function calls that pass around numbers. But now, let's make it collapse.</p>
<p>If we think about it for a second, we'll realize that there are three criteria we need to meet in order to collapse:</p>
<ol>
<li>We need the content to be moving up. The user's finger needs to be moving the scrollview upwards.</li>
<li>We need for to be within the bounds of the content, not beyond it. (This is more important in the expanding logic.)</li>
<li>We need for there to be room to continue collapsing the header. If it is already as small as it can go, we can't collapse any more.</li>
</ol>
<pre><code class="lang-swift"><span class="hljs-comment">// In HeaderView.swift</span>

<span class="hljs-comment">// in updateHeader(newY:oldY)</span>
<span class="hljs-keyword">let</span> delta = newY - oldY

<span class="hljs-keyword">let</span> isMovingUp = delta &gt; <span class="hljs-number">0</span>
<span class="hljs-keyword">let</span> isInContent = newY &gt; <span class="hljs-number">0</span>
<span class="hljs-keyword">let</span> hasRoomToCollapse = currentOffset &gt; minHeight
<span class="hljs-keyword">let</span> shouldCollapse = isMovingUp &amp;&amp; isInContent &amp;&amp; hasRoomToCollapse

<span class="hljs-keyword">if</span> shouldCollapse {
    currentOffset -= delta
    <span class="hljs-keyword">return</span> newY - delta
}
</code></pre>
<p>This won't compile yet, because we need to add a couple of header variables, but you should be able to see the logic. If we meet all the criteria for collapsing, we'll shrink the header by the amount the user has moved their finger, and we'll return an updated offset so the scroll view can move along with it.</p>
<p>Now we need to add a few more things to get this to work. We'll add a <code>minHeight</code> and <code>maxHeight</code> to keep track of the bounds of our header. We'll also add a <code>heightConstraint</code> variable, so we can update the constrained height as we need to. Finally, we'll add <code>currentOffset</code> which will just be a wrapper around the height constraint and which will call our animation function any time it changes.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HeaderView.swift</span>

<span class="hljs-keyword">private</span> <span class="hljs-built_in">lazy</span> <span class="hljs-keyword">var</span> minHeight: <span class="hljs-type">CGFloat</span> = { <span class="hljs-number">44</span> + <span class="hljs-number">12</span> + <span class="hljs-number">12</span> + safeAreaInsets.top }()
<span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> maxHeight: <span class="hljs-type">CGFloat</span> = <span class="hljs-number">600</span>
<span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> heightConstraint = <span class="hljs-type">NSLayoutConstraint</span>()

<span class="hljs-comment">// in constrain()</span>
heightConstraint = heightAnchor == maxHeight

<span class="hljs-comment">// in animation extension</span>
<span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> currentOffset: <span class="hljs-type">CGFloat</span> {
    <span class="hljs-keyword">get</span> { heightConstraint.constant }
    <span class="hljs-keyword">set</span> { animate(to: newValue) }
}

<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">animate</span><span class="hljs-params">(to value: CGFloat)</span></span> {
    <span class="hljs-keyword">let</span> clamped = <span class="hljs-built_in">max</span>(<span class="hljs-built_in">min</span>(value, maxHeight), minHeight)
    heightConstraint.constant = clamped
}
</code></pre>
<p>With that, you should be able to run the app and see the header collapse as you scroll the collection view up.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1618015460244/szs2cG-N5.gif" alt="Collapsing Header.gif" /></p>
<h2 id="expanding-the-header">Expanding The Header</h2>
<p>You may notice that it is only one way right now. You can collapse the header, but you can't expand it back without restarting the app. That will be the next problem we tackle. Again, we have three criteria we need to meet.</p>
<ol>
<li>We need the content to be moving down.</li>
<li>We need to be beyond the scrollview's content. This means we won't expand the header any time the user scrolls down, we'll only expand it if they are at the end of the content.</li>
<li>We need to have room to expand the header.</li>
</ol>
<pre><code class="lang-swift"><span class="hljs-comment">// In HeaderView.swift</span>

<span class="hljs-comment">// in updateHeader(newY:oldY:)</span>
<span class="hljs-keyword">let</span> isMovingDown = delta &lt; <span class="hljs-number">0</span>
<span class="hljs-keyword">let</span> isBeyondContent = newY &lt; <span class="hljs-number">0</span>
<span class="hljs-keyword">let</span> hasRoomToExpand = currentOffset &lt; maxHeight
<span class="hljs-keyword">let</span> shouldExpand = isMovingDown &amp;&amp; isBeyondContent &amp;&amp; hasRoomToExpand

<span class="hljs-keyword">if</span> shouldCollapse || shouldExpand {
    currentOffset -= delta
    <span class="hljs-keyword">return</span> newY - delta
}
</code></pre>
<p>The logic for what we need to do if we want to expand will be the same as when we collapse, because the delta will be negative so it will have the effect of adding to the <code>currentOffset</code>. That means we can put it all in one if block.</p>
<h2 id="polishing-the-animation">Polishing The Animation</h2>
<p>Now we've got it working, but it does not quite match the spec. The card needs to move up and the search container needs to fade in, but neither of them start until we're halfway through the movement. So let's take a couple of steps to match that. First, let's get a normalized value of where we're at in the transition. That means a number from 0.0 to 1.0 which will reflect what percentage of the way through we currently are. Then, we'll use that to break out into a function that will handle animations 0-50% and another that will handle 50-100%.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HeaderView.swift</span>

<span class="hljs-comment">//in animate(to:)</span>
<span class="hljs-keyword">let</span> normalized = (value - minHeight) / (maxHeight - minHeight)
<span class="hljs-keyword">switch</span> normalized {
<span class="hljs-keyword">case</span> ..&lt;<span class="hljs-number">0.5</span>:
    animateToFifty(normalized)
<span class="hljs-keyword">default</span>:
    animateToOneHundred(normalized)
}

<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">animateToFifty</span><span class="hljs-params">(<span class="hljs-number">_</span> normalized: CGFloat)</span></span> {
    <span class="hljs-keyword">let</span> newTop = normalized * <span class="hljs-number">2</span> * maxTopSpace
    topSpaceConstraint.constant = newTop
    searchContainer.setBackgroundAlpha(<span class="hljs-number">1</span> - normalized * <span class="hljs-number">2</span>)
}

<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">animateToOneHundred</span><span class="hljs-params">(<span class="hljs-number">_</span> normalized: CGFloat)</span></span> {
    topSpaceConstraint.constant = maxTopSpace
    searchContainer.setBackgroundAlpha(<span class="hljs-number">0</span>)
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1618015577313/7QoHOwkWZ.gif" alt="Collapsing and Expanding Header.gif" /></p>
<h2 id="updating-the-status-bar">Updating The Status Bar</h2>
<p>Now that's looking pretty good! The last thing that we need to fix is the status bar. It doesn't automatically update when we put white content behind it, so we need to do that update ourselves. As we discussed earlier though, we need to handle that in the view controller. So we'll need to send delegate methods up to let the view controller know that it might want to update the status bar. First, let's define a delegate protocol, add a delegate to our view and a local property to keep track of what the view thinks the style should be.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HeaderView.swift</span>

<span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">HeaderViewDelegate</span>: <span class="hljs-title">AnyObject</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">updateStatusBarStyle</span><span class="hljs-params">(to style: UIStatusBarStyle)</span></span>
}

<span class="hljs-comment">//in HeaderView</span>
<span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> delegate: <span class="hljs-type">HeaderViewDelegate?</span>

<span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> statusBarStyle: <span class="hljs-type">UIStatusBarStyle</span> = .lightContent {
    <span class="hljs-keyword">didSet</span> { delegate?.updateStatusBarStyle(to: statusBarStyle) }
}
</code></pre>
<p>Then we need to move up a layer to the <code>HomeView</code> and do the same thing, to pass the message along:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HomeView.swift</span>

<span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">HomeViewDelegate</span>: <span class="hljs-title">AnyObject</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">updateStatusBarStyle</span><span class="hljs-params">(to style: UIStatusBarStyle)</span></span>
}

<span class="hljs-comment">// in HomeView</span>
<span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> delegate: <span class="hljs-type">HomeViewDelegate?</span>

<span class="hljs-comment">// in configure()</span>
headerView.delegate = <span class="hljs-keyword">self</span>

<span class="hljs-comment">// at the bottom of the file</span>
<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">HomeView</span>: <span class="hljs-title">HeaderViewDelegate</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">updateStatusBarStyle</span><span class="hljs-params">(to style: UIStatusBarStyle)</span></span> {
        delegate?.updateStatusBarStyle(to: style)
    }
}
</code></pre>
<p>Then we need to adopt the delegate protocol in the <code>HomeViewController</code></p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HomeViewController.swift</span>

<span class="hljs-comment">// in loadView()</span>
contentView.delegate = <span class="hljs-keyword">self</span>

<span class="hljs-comment">// at the bottom of the file</span>
<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">HomeViewController</span>: <span class="hljs-title">HomeViewDelegate</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">updateStatusBarStyle</span><span class="hljs-params">(to style: UIStatusBarStyle)</span></span> {
        statusBarStyle = style
        <span class="hljs-type">UIView</span>.animate(withDuration: <span class="hljs-number">0.4</span>) {
            <span class="hljs-keyword">self</span>.setNeedsStatusBarAppearanceUpdate()
        }
    }
}
</code></pre>
<p>Finally, we just need to make a check in <code>animateToFifty</code> to see if we should change the status bar style or not.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HeaderView.swift</span>

<span class="hljs-keyword">if</span> newTop &lt; <span class="hljs-number">24</span> &amp;&amp; statusBarStyle != .darkContent {
    statusBarStyle = .darkContent
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> newTop &gt; <span class="hljs-number">24</span> &amp;&amp; statusBarStyle != .lightContent {
    statusBarStyle = .lightContent
}
</code></pre>
<p>I picked 24 points as a rough estimate of when the card passes by the status bar. It's not perfect, but it still feels pretty good.</p>
<p>There is just one minor problem left, and it is one that the actual Airbnb app doesn't have to handle. In our app, we are supporting dark mode (as good iOS citizens should), but in even in dark mode we want the space behind the card to be black. That means the status bar style is right when the header is expanded (it should still be white), but it should also stay white when the header is collapsed, because the search container background is now black too. All we need to do to fix that is add a check in the view controller. If the new status bar style is light and the user interface style is dark, we'll just return early:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HomeViewController.swift</span>

<span class="hljs-comment">// in updateStatusBarStyle(to:)</span>
<span class="hljs-keyword">if</span> statusBarStyle == .lightContent &amp;&amp; traitCollection.userInterfaceStyle == .dark {
    <span class="hljs-keyword">return</span>
}
</code></pre>
<p>And now it looks great! It matches the spec, and it even works nicely in dark mode.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1618015594729/4gGBSIX6M.gif" alt="Collapsing Header - Light.gif" />
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1618015600681/pq7cwLyJk.gif" alt="Collapsing Header - Dark.gif" /></p>
<h2 id="caveats">Caveats</h2>
<ul>
<li>There are still some parts of the animation that aren't exactly right. In the real version, it bounces past the final height of the header if you pull on it, and you can swipe in the header itself to do the animation. Both of those things should be accomplishable within the framework I've set up, but I'll leave that as an exercise for you.</li>
<li>In the actual Airbnb app the "Explore nearby stays" button animates a little bit when it is highlighted, rather than changing the color of the text. I didn't want to take the time to go into that here, but it should be pretty simple to add.</li>
</ul>
<h2 id="wrap-up">Wrap Up</h2>
<p>In this article we laid out the header view, we looked at one technique for animating a header based on the user's interaction in a scroll view, and we looked at some techniques for customizing that animation that keep our code fairly straightforward and easy to read. This is the last article in this series and I hope that you learned something or at least had fun. See you in the next one!</p>
<hr />
<p>Check out the code up to this point in <a target="_blank" href="https://github.com/dillon-mce/Airbnb-Home/tree/part-6">this repo</a>.</p>
<div class="hn-embed-widget" id="buyacoffee"></div>]]></content:encoded></item><item><title><![CDATA[Building Airbnb's UI From Scratch - Part 5]]></title><description><![CDATA[In this series of posts I am going to be building out the UI for Airbnb's "Explore" tab from scratch. This is an exercise in figuring out how to achieve the different layouts and effects you find in popular apps and I chose Airbnb because I thought i...]]></description><link>https://dilloncodes.com/airbnb-part-5</link><guid isPermaLink="true">https://dilloncodes.com/airbnb-part-5</guid><category><![CDATA[Swift]]></category><category><![CDATA[iOS]]></category><category><![CDATA[Xcode]]></category><category><![CDATA[UI]]></category><dc:creator><![CDATA[Dillon McElhinney]]></dc:creator><pubDate>Fri, 28 May 2021 19:28:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1618036936607/IUS7uhvxG.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this series of posts I am going to be building out the UI for Airbnb's "Explore" tab from scratch. This is an exercise in figuring out how to achieve the different layouts and effects you find in popular apps and I chose <a target="_blank" href="https://apps.apple.com/us/app/airbnb/id401626263?uo=4">Airbnb</a> because I thought it had several great examples. It is not intended to be used for anything other than for educational purposes and all my code will be available at <a target="_blank" href="https://github.com/dillon-mce/Airbnb-Home">this repo</a> if you want to follow along and build it yourself. A few disclaimers: </p>
<ul>
<li>For all the illustrations in the app I just took screenshots of the actual app and cropped them to size. They are not the full versions of the illustrations and they are not really even formatted correctly for use in an iOS app. I tried to spend as little time as possible on that prep. </li>
<li>We will get the fonts, colors and icons as close as we can with just using the system versions. I am pretty sure the font and icons that Airbnb actually uses would require a license and I don't care that much about making this exact. With the way we organize it, it would be pretty easy to swap in the real ones if you want to take that step yourself.</li>
<li>We will not do any business logic. We will hard code all the data and not handle any user actions. Again, this is not meant to be production code, just an exploration of some UI techniques.</li>
<li>There is a good chance that Airbnb will look different by the time you see this. Honestly, there's a non-zero chance that it will change before I finish writing this, so the live app will probably look different than what you see me build, but the layout/principles should be pretty much the same. <em>(Editorial note: it has already changed before I was able to finish writing this series, but I have a few screenshots of what the app looked like before, and we'll just build up to the spec that I created.)</em></li>
</ul>
<p>Here's what our final product will look like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1618017103718/39dgJOE5X.gif" alt="Design Spec.gif" /></p>
<p>With all that said, let's go.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href=" https://youtu.be/_olzmg8GU7M "> https://youtu.be/_olzmg8GU7M </a></div>
<h1 id="the-footer">The Footer</h1>
<p>The last section we need to implement for the collection view is the footer. This section will be pretty similar to the first one, only with four items per group instead of two. Lets add a cell for that section:</p>
<pre><code><span class="hljs-comment">// In Colleciton View Elements/FooterCell.swift</span>

<span class="hljs-keyword">typealias</span> <span class="hljs-type">FooterCell</span> = <span class="hljs-type">ContentCell</span>&lt;<span class="hljs-type">FooterView</span>&gt;

<span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FooterView</span>: <span class="hljs-title">ProgrammaticView</span>, <span class="hljs-title">ContentConfiguringView</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> stack = <span class="hljs-type">UIStackView</span>()
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> titleLabel = <span class="hljs-type">UILabel</span>()
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> subtitleLabel = <span class="hljs-type">UILabel</span>()

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">()</span></span> {
        stack.axis = .vertical
        stack.spacing = <span class="hljs-number">4</span>

        titleLabel.font = .custom(style: .headline)
        subtitleLabel.font = .custom(style: .subheadline)
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">constrain</span><span class="hljs-params">()</span></span> {
        addSubviews(stack)
        stack.addArrangedSubviews(titleLabel, subtitleLabel)

        stack.horizontalAnchors == horizontalAnchors
        stack.verticalAnchors == verticalAnchors + <span class="hljs-number">28</span>
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">(with content: Content?)</span></span> {
        titleLabel.text = content?.title
        subtitleLabel.text = content?.subtitle

        <span class="hljs-keyword">switch</span> content?.style {
        <span class="hljs-keyword">case</span> .standard: titleLabel.font = .custom(style: .headline)
        <span class="hljs-keyword">case</span> .title: titleLabel.font = .custom(style: .title4)
        <span class="hljs-keyword">case</span> .<span class="hljs-keyword">none</span>: <span class="hljs-keyword">break</span>
        }
    }
}
</code></pre><h2 id="the-footer-section">The Footer Section</h2>
<p>We'll need a background for that section, since it is a different color. Fortunately, the infrastructure we set up in the last part will make this very easy:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Collection View Elements/BackgroundView.swift</span>

<span class="hljs-comment">// add view</span>
<span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SecondaryBackgroundView</span>: <span class="hljs-title">BackgroundView</span> </span>{
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">provideBackgroundColor</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">UIColor?</span> {
        .secondarySystemBackground
    }
}

<span class="hljs-comment">// add BackgroundStyle case</span>
<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">BackgroundStyle</span>: <span class="hljs-title">String</span>, <span class="hljs-title">CaseIterable</span> </span>{
    <span class="hljs-keyword">case</span> inverted, secondary

    <span class="hljs-keyword">var</span> elementKind: <span class="hljs-type">String</span> { <span class="hljs-string">"background.\(rawValue)"</span> }

    <span class="hljs-keyword">var</span> viewClass: <span class="hljs-type">AnyClass</span> {
        <span class="hljs-keyword">switch</span> <span class="hljs-keyword">self</span> {
        <span class="hljs-keyword">case</span> .inverted: <span class="hljs-keyword">return</span> <span class="hljs-type">InvertedBackgroundView</span>.<span class="hljs-keyword">self</span>
        <span class="hljs-keyword">case</span> .secondary: <span class="hljs-keyword">return</span> <span class="hljs-type">SecondaryBackgroundView</span>.<span class="hljs-keyword">self</span>
        }
    }
}
</code></pre>
<p>Then we just need to add a layout section for it:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Collection View Elements/NSCollectionLayoutSection+Layouts.swift</span>

<span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">footer</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">NSCollectionLayoutSection</span> {
    <span class="hljs-keyword">let</span> itemSize = <span class="hljs-type">NSCollectionLayoutSize</span>(widthDimension: .fractionalWidth(<span class="hljs-number">1</span>),
                                          heightDimension: .estimated(<span class="hljs-number">60</span>))
    <span class="hljs-keyword">let</span> item = <span class="hljs-type">NSCollectionLayoutItem</span>(layoutSize: itemSize)

    <span class="hljs-keyword">let</span> groupSize = <span class="hljs-type">NSCollectionLayoutSize</span>(widthDimension: .fractionalWidth(<span class="hljs-number">0.7</span>),
                                           heightDimension: .estimated(<span class="hljs-number">200</span>))
    <span class="hljs-keyword">let</span> group = <span class="hljs-type">NSCollectionLayoutGroup</span>.vertical(layoutSize: groupSize,
                                                 subitems: [item, item, item, item])

    <span class="hljs-keyword">let</span> headerSize = <span class="hljs-type">NSCollectionLayoutSize</span>(widthDimension: .fractionalWidth(<span class="hljs-number">1</span>),
                                            heightDimension: .estimated(<span class="hljs-number">100</span>))
    <span class="hljs-keyword">let</span> header = <span class="hljs-type">NSCollectionLayoutBoundarySupplementaryItem</span>.header(layoutSize: headerSize)

    <span class="hljs-keyword">let</span> section = <span class="hljs-type">NSCollectionLayoutSection</span>(group: group)
    section.orthogonalScrollingBehavior = .groupPaging
    section.boundarySupplementaryItems = [header]
    section.interGroupSpacing = <span class="hljs-number">12</span>
    section.contentInsets = .<span class="hljs-keyword">init</span>(top: <span class="hljs-number">0</span>, leading: <span class="hljs-number">20</span>, bottom: <span class="hljs-number">20</span>, trailing: <span class="hljs-number">20</span>)

    section.addBackground(style: .secondary)

    <span class="hljs-keyword">return</span> section
}
</code></pre>
<h2 id="showing-the-footer">Showing The Footer</h2>
<p>To actually see it, we'll need to uncomment the footer section and the rest of the stubbed data:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Models/Content.swift</span>

<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">Section</span>: <span class="hljs-title">Int</span>, <span class="hljs-title">Hashable</span>, <span class="hljs-title">CaseIterable</span> </span>{
    <span class="hljs-keyword">case</span> nearby, stays, experiences, hosting, info
}

<span class="hljs-comment">// in headerContent() switch statment</span>
<span class="hljs-keyword">case</span> .info: <span class="hljs-keyword">return</span> .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Stay informed"</span>, subtitle: <span class="hljs-literal">nil</span>, image: <span class="hljs-literal">nil</span>)

<span class="hljs-comment">// in stubData() switch statment</span>
<span class="hljs-keyword">case</span> .info:
    <span class="hljs-keyword">return</span> [
        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"For guests"</span>, subtitle: <span class="hljs-literal">nil</span>, image: <span class="hljs-literal">nil</span>, style: .title),
        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Our COVID-19 response"</span>,
              subtitle: <span class="hljs-string">"Health and saftey updates"</span>,
              image: <span class="hljs-literal">nil</span>),
        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Cancellation options"</span>,
              subtitle: <span class="hljs-string">"Learn what's covered"</span>,
              image: <span class="hljs-literal">nil</span>),
        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Help Center"</span>,
              subtitle: <span class="hljs-string">"Get support"</span>,
              image: <span class="hljs-literal">nil</span>),

        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"For hosts"</span>, subtitle: <span class="hljs-literal">nil</span>, image: <span class="hljs-literal">nil</span>, style: .title),
        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Message from Brian Chesky"</span>,
              subtitle: <span class="hljs-string">"Hear from our CEO"</span>,
              image: <span class="hljs-literal">nil</span>),
        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Resources for hosting"</span>,
              subtitle: <span class="hljs-string">"What's impacted by COVID-19"</span>,
              image: <span class="hljs-literal">nil</span>),
        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Providing frontline stays"</span>,
              subtitle: <span class="hljs-string">"Learn how to help"</span>,
              image: <span class="hljs-literal">nil</span>),

        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"For COVID-19 responders"</span>, subtitle: <span class="hljs-literal">nil</span>, image: <span class="hljs-literal">nil</span>, style: .title),
        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Frontline stays"</span>,
              subtitle: <span class="hljs-string">"Learn about our program"</span>,
              image: <span class="hljs-literal">nil</span>),
        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Sign up"</span>,
              subtitle: <span class="hljs-string">"Check for housing options"</span>,
              image: <span class="hljs-literal">nil</span>),
        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Make a donation"</span>,
              subtitle: <span class="hljs-string">"Support nonprofit organizations"</span>,
              image: <span class="hljs-literal">nil</span>),

        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"More"</span>, subtitle: <span class="hljs-literal">nil</span>, image: <span class="hljs-literal">nil</span>, style: .title),
        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Airbnb Newsroom"</span>,
              subtitle: <span class="hljs-string">"Latest announcements"</span>,
              image: <span class="hljs-literal">nil</span>),
        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"World Health Organization"</span>,
              subtitle: <span class="hljs-string">"Education and updates"</span>,
              image: <span class="hljs-literal">nil</span>),
        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Project Lighthouse"</span>,
              subtitle: <span class="hljs-string">"Finding and fighting discrimination"</span>,
              image: <span class="hljs-literal">nil</span>),
    ]
</code></pre>
<p>And we'll need to update the collection view and data source to show the content in the right section:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HomeView.swift</span>

<span class="hljs-comment">// in makeCollectionView() switch statement</span>
<span class="hljs-keyword">case</span> .info:
    <span class="hljs-keyword">return</span> .footer()

<span class="hljs-comment">// in makeDataSource() switch statement</span>
<span class="hljs-keyword">case</span> .info:
    <span class="hljs-keyword">let</span> registration = <span class="hljs-type">FooterCell</span>.registration()
    <span class="hljs-keyword">return</span> view.dequeueConfiguredReusableCell(using: registration,
                                              <span class="hljs-keyword">for</span>: indexPath,
                                              item: item)
</code></pre>
<p>And with that, the footer section is visible and looks pretty good. The only thing we're missing is the separators.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1617839964351/qs92dXZ8F.png" alt="Screen Shot 2021-03-07 at 4.09.41 PM.png" /></p>
<h2 id="adding-separators">Adding Separators</h2>
<p>I went back and forth on the best way to implement this and where I landed was to break it up into a couple of parts so things can stay decoupled. We'll define another protocol to provide a method to show a separator or not, we'll let individual cells decide if they want to have a separator at all, and we'll pass a closure as an argument to <code>registration</code> to allow the data source to define if a specific cell should show its separator or not.</p>
<p>First, let's add a view to the <code>FooterCell</code> that will be our separator:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Colleciton View Elements/FooterCell.swift</span>

<span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> separator = <span class="hljs-type">UIView</span>()

<span class="hljs-comment">// in configure()</span>
separator.backgroundColor = .quaternaryLabel

<span class="hljs-comment">// in constrain()</span>
addSubviews(stack, separator)
<span class="hljs-comment">// other stuff...</span>
separator.topAnchor == bottomAnchor
separator.horizontalAnchors == stack.horizontalAnchors
separator.heightAnchor == <span class="hljs-number">1</span>
</code></pre>
<p>Then we'll add a protocol with the <code>showSeparator</code> method and have our content cell forward it on to the view, if the view has adopted the protocol:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Colleciton View Elements/ContentCell.swift</span>

<span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">SeparatorShowing</span>: <span class="hljs-title">AnyObject</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">showSeparator</span><span class="hljs-params">(<span class="hljs-number">_</span> shouldShow: Bool)</span></span>
}

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">ContentCell</span>: <span class="hljs-title">SeparatorShowing</span> <span class="hljs-title">where</span> <span class="hljs-title">View</span>: <span class="hljs-title">SeparatorShowing</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">showSeparator</span><span class="hljs-params">(<span class="hljs-number">_</span> shouldShow: Bool)</span></span> {
        view.showSeparator(shouldShow)
    }
}
</code></pre>
<p>Then we need our <code>FooterView</code> to conform to this protocol:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Colleciton View Elements/FooterCell.swift</span>

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">FooterView</span>: <span class="hljs-title">SeparatorShowing</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">showSeparator</span><span class="hljs-params">(<span class="hljs-number">_</span> shouldShow: Bool)</span></span> {
        separator.alpha = shouldShow ? <span class="hljs-number">0.8</span> : <span class="hljs-number">0</span>
    }
}
</code></pre>
<p>Then we need a way to decide if we should show the separator or not. In this case, we'll want to show it for the items at indexes 0, 1, 2 and hide it for 3, then show it for 4, 5, 6 and hide it for 7, etc. We have access to that index in the registration, but really the data source should be the one making the decision so we'll pass the index back to the data source in a closure and get a boolean back:</p>
<pre><code class="lang-swift">
<span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">registration</span><span class="hljs-params">(showSeparator: @escaping <span class="hljs-params">(IndexPath)</span></span></span> -&gt; <span class="hljs-type">Bool</span> = { <span class="hljs-number">_</span> <span class="hljs-keyword">in</span> <span class="hljs-literal">false</span> }) -&gt; <span class="hljs-type">UICollectionView</span>.<span class="hljs-type">CellRegistration</span>&lt;<span class="hljs-type">ContentCell</span>&lt;<span class="hljs-type">View</span>&gt;, <span class="hljs-type">Content</span>&gt; {
    <span class="hljs-type">UICollectionView</span>.<span class="hljs-type">CellRegistration</span> { cell, indexPath, content <span class="hljs-keyword">in</span>
        cell.configure(with: content)
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> cell = cell <span class="hljs-keyword">as</span>? <span class="hljs-type">SeparatorShowing</span> {
            <span class="hljs-keyword">let</span> shouldShow = showSeparator(indexPath)
            cell.showSeparator(shouldShow)
        }
    }
}
</code></pre>
<p>Giving the closure a default value will mean that we only need to provide this if we care about it, which is good because we only care about it in this one section. And casting the cell as <code>SeparatorShowing</code> before calling that closure ensures that it only gets called if the cell can actually do something with the information.</p>
<p>Now we just need to update <code>makeDataSource</code> to provide the closue in the <code>.info</code> section:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HomeView.swift</span>

<span class="hljs-keyword">case</span> .info:
    <span class="hljs-keyword">let</span> registration = <span class="hljs-type">FooterCell</span>.registration() { indexPath <span class="hljs-keyword">in</span>
        indexPath.item % <span class="hljs-number">4</span> != <span class="hljs-number">3</span>
    }
</code></pre>
<p>If you're not familiar with modulo math this check will divide the item index by 4 and look at the remainder. If it is 3, we'll return <code>false</code> (to hide the separator) and otherwise we'll return <code>true</code> (to show the separator). If you try that out with the sequence of numbers outlined above, you'll find that this leads to the exact sequence we wanted. Or you can just run the app and see it for yourself.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1617840230223/48Kb2Ictx.png" alt="Screen Shot 2021-03-07 at 4.09.21 PM.png" /></p>
<h2 id="wrap-up">Wrap Up</h2>
<p>In this post we added a footer cell, we added a footer section with a background color, and we looked at one method for adding separators to cells. In the last part, we'll totally shift gears and get the collapsing header set up!</p>
<hr />
<p>Check out the code up to this point in <a target="_blank" href="https://github.com/dillon-mce/Airbnb-Home/tree/part-5">this repo</a>.</p>
<div class="hn-embed-widget" id="buyacoffee"></div>]]></content:encoded></item><item><title><![CDATA[Building Airbnb's UI From Scratch - Part 4]]></title><description><![CDATA[In this series of posts I am going to be building out the UI for Airbnb's "Explore" tab from scratch. This is an exercise in figuring out how to achieve the different layouts and effects you find in popular apps and I chose Airbnb because I thought i...]]></description><link>https://dilloncodes.com/airbnb-part-4</link><guid isPermaLink="true">https://dilloncodes.com/airbnb-part-4</guid><category><![CDATA[iOS]]></category><category><![CDATA[Swift]]></category><category><![CDATA[Xcode]]></category><category><![CDATA[UI]]></category><dc:creator><![CDATA[Dillon McElhinney]]></dc:creator><pubDate>Mon, 24 May 2021 01:03:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1618036931794/HVMv0fVbs.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this series of posts I am going to be building out the UI for Airbnb's "Explore" tab from scratch. This is an exercise in figuring out how to achieve the different layouts and effects you find in popular apps and I chose <a target="_blank" href="https://apps.apple.com/us/app/airbnb/id401626263?uo=4">Airbnb</a> because I thought it had several great examples. It is not intended to be used for anything other than for educational purposes and all my code will be available at <a target="_blank" href="https://github.com/dillon-mce/Airbnb-Home">this repo</a> if you want to follow along and build it yourself. A few disclaimers: </p>
<ul>
<li>For all the illustrations in the app I just took screenshots of the actual app and cropped them to size. They are not the full versions of the illustrations and they are not really even formatted correctly for use in an iOS app. I tried to spend as little time as possible on that prep. </li>
<li>We will get the fonts, colors and icons as close as we can with just using the system versions. I am pretty sure the font and icons that Airbnb actually uses would require a license and I don't care that much about making this exact. With the way we organize it, it would be pretty easy to swap in the real ones if you want to take that step yourself.</li>
<li>We will not do any business logic. We will hard code all the data and not handle any user actions. Again, this is not meant to be production code, just an exploration of some UI techniques.</li>
<li>There is a good chance that Airbnb will look different by the time you see this. Honestly, there's a non-zero chance that it will change before I finish writing this, so the live app will probably look different than what you see me build, but the layout/principles should be pretty much the same. <em>(Editorial note: it has already changed before I was able to finish writing this series, but I have a few screenshots of what the app looked like before, and we'll just build up to the spec that I created.)</em></li>
</ul>
<p>Here's what our final product will look like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1618017103718/39dgJOE5X.gif" alt="Design Spec.gif" /></p>
<p>With all that said, let's go.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/V8hA-5KZUxY ">https://youtu.be/V8hA-5KZUxY </a></div>
<h1 id="backgrounds-inverted-sections-and-dark-mode">Backgrounds, Inverted Sections and Dark Mode</h1>
<h2 id="inverted-colors">Inverted Colors</h2>
<p>The next thing we're missing from the spec is the dark section. In the pre-dark-mode era we probably would have considered this a "dark" section and the others "light" sections and given them colors accordingly. What I would like to do is consider this an "inverted" section so that it stands out equally whether the user is looking at the app in dark mode or light mode. (Note, the actual Airbnb app does not support dark mode as I write this, so hopefully they'll steal my ideas.) Fortunately, this is pretty easy to do with the API Apple has given us for colors, just by adding a couple of extensions. First we're going to add an extension on <code>UITraitCollection</code> which returns itself, but with just the <code>userInterfaceStyle</code> flipped:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Extensions/UITraitCollection+Inverted.swift</span>

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">UITraitCollection</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> lightStyle: <span class="hljs-type">UITraitCollection</span> = .<span class="hljs-keyword">init</span>(userInterfaceStyle: .light)
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> darkStyle: <span class="hljs-type">UITraitCollection</span> = .<span class="hljs-keyword">init</span>(userInterfaceStyle: .dark)

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">invertedStyle</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">UITraitCollection</span> {
        <span class="hljs-keyword">switch</span> userInterfaceStyle {
        <span class="hljs-keyword">case</span> .dark:
            <span class="hljs-keyword">return</span> <span class="hljs-type">UITraitCollection</span>.<span class="hljs-keyword">init</span>(traitsFrom: [<span class="hljs-keyword">self</span>, <span class="hljs-type">Self</span>.lightStyle])
        <span class="hljs-keyword">case</span> .light, .unspecified:
            <span class="hljs-keyword">return</span> <span class="hljs-type">UITraitCollection</span>.<span class="hljs-keyword">init</span>(traitsFrom: [<span class="hljs-keyword">self</span>, <span class="hljs-type">Self</span>.darkStyle])
        @unknown <span class="hljs-keyword">default</span>:
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">self</span>
        }
    }
}
</code></pre>
<p>Then we'll add a couple of "inverted" versions of the static colors on <code>UIColor</code>:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Extensions/UIColor+Inverted.swift</span>

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">UIColor</span> </span>{
    <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> invertedBackground: <span class="hljs-type">UIColor</span> = .<span class="hljs-keyword">init</span> { traits <span class="hljs-keyword">in</span>
        systemBackground.resolvedColor(with: traits.invertedStyle())
    }

    <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> invertedLabel: <span class="hljs-type">UIColor</span> = .<span class="hljs-keyword">init</span> { traits <span class="hljs-keyword">in</span>
        label.resolvedColor(with: traits.invertedStyle())
    }
}
</code></pre>
<p>And now we have versions of the system provided semantic colors that will render for the opposite user interface style that we are currently in.</p>
<h2 id="using-the-colors">Using The Colors</h2>
<p>Now, to render the <code>LargeSquareCell</code> with an inverted label color, we'll need a variant of that class which uses the inverted color. To do that, I'm going to use a combination of subclassing and our cell wrapper. First, we'll define a <code>ColorStyle</code> to determine if we should render the view in the standard way or in the inverted way:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Extensions/UIColor+Inverted.swift</span>

<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">ColorStyle</span> </span>{
    <span class="hljs-keyword">case</span> standard, inverted
}
</code></pre>
<p>Next, we'll add a color style property to the <code>LargeSquareCell</code>:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Collection View Elements/LargeSquareCell.swift</span>

<span class="hljs-keyword">private</span> <span class="hljs-built_in">lazy</span> <span class="hljs-keyword">var</span> style: <span class="hljs-type">ColorStyle</span> = provideStyle()

<span class="hljs-comment">// in configure</span>
<span class="hljs-keyword">let</span> textColor: <span class="hljs-type">UIColor</span> = style == .inverted ? .invertedLabel : .label
titleLabel.font = .custom(style: .headline)
titleLabel.textColor = textColor
subtitleLabel.font = .custom(style: .subheadline)
subtitleLabel.textColor = textColor

<span class="hljs-comment">// at bottom of class</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">provideStyle</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">ColorStyle</span> { .standard }
</code></pre>
<p>This may look a little weird, but it gives us everything we need to define an inverted version in just a couple of lines:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Collection View Elements/LargeSquareCell.swift</span>

<span class="hljs-keyword">typealias</span> <span class="hljs-type">InvertedLargeSquareCell</span> = <span class="hljs-type">ContentCell</span>&lt;<span class="hljs-type">InvertedLargeSquareView</span>&gt;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">InvertedLargeSquareView</span>: <span class="hljs-title">LargeSquareView</span> </span>{
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">provideStyle</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">ColorStyle</span> { .inverted }
}
</code></pre>
<p>Everything about this cell will be exactly the same as <code>LargeSquareCell</code>, with the only difference being this one will use inverted colors. And we'll do the same thing to make an inverted header:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Collection View Elements/SectionHeader.swift</span>

<span class="hljs-keyword">private</span> <span class="hljs-built_in">lazy</span> <span class="hljs-keyword">var</span> style: <span class="hljs-type">ColorStyle</span> = provideStyle()

<span class="hljs-comment">// in configure</span>
<span class="hljs-keyword">let</span> textColor: <span class="hljs-type">UIColor</span> = style == .inverted ? .invertedLabel : .label

titleLabel.textColor = textColor

subtitleLabel.textColor = textColor

<span class="hljs-comment">// at bottom of class</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">provideStyle</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">ColorStyle</span> { .standard }
</code></pre>
<p>And then we'll add the <code>InvertedHeader</code></p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Collection View Elements/SectionHeader.swift</span>

<span class="hljs-keyword">typealias</span> <span class="hljs-type">InvertedHeader</span> = <span class="hljs-type">ContentHeader</span>&lt;<span class="hljs-type">InvertedHeaderView</span>&gt;

<span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">InvertedHeaderView</span>: <span class="hljs-title">SectionHeaderView</span> </span>{
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">provideStyle</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">ColorStyle</span> { .inverted }
}
</code></pre>
<h2 id="adding-a-background">Adding A Background</h2>
<p>Now we need a way to change the color of the background of one section. In <code>UICollectionView</code>s this is done with a decorator item, which needs to be a subclass of <code>UICollectionViewReusableView</code>. We're going to do a similar subclassing thing here as well, because it will make it easier when we need to add the background for the footer section. First we'll define a <code>BackgroundView</code> class to be the parent for all of our backgrounds:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Collection View Elements/BackgroundView.swift</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BackgroundView</span>: <span class="hljs-title">UICollectionReusableView</span> </span>{

    <span class="hljs-keyword">override</span> <span class="hljs-keyword">init</span>(frame: <span class="hljs-type">CGRect</span>) {
        <span class="hljs-keyword">super</span>.<span class="hljs-keyword">init</span>(frame: frame)
        backgroundColor = provideBackgroundColor()
    }

    <span class="hljs-keyword">required</span> <span class="hljs-keyword">init</span>?(coder: <span class="hljs-type">NSCoder</span>) {
        <span class="hljs-keyword">super</span>.<span class="hljs-keyword">init</span>(coder: coder)
        backgroundColor = provideBackgroundColor()
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">provideBackgroundColor</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">UIColor?</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
    }
}
</code></pre>
<p>And then we'll define the background with inverted colors:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Collection View Elements/BackgroundView.swift</span>

<span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">InvertedBackgroundView</span>: <span class="hljs-title">BackgroundView</span> </span>{
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">provideBackgroundColor</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">UIColor?</span> {
        .invertedBackground
    }
}
</code></pre>
<p>And we'll add some helpers which will make things a little more convenient when we're defining sections. We'll add an enum where we define all of our background styles, which will provide a name to use for the "element kind", so we can avoid typing strings when we don't need to, and will map the enum cases to which class of background we should use.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Collection View Elements/BackgroundView.swift</span>

<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">BackgroundStyle</span>: <span class="hljs-title">String</span>, <span class="hljs-title">CaseIterable</span> </span>{
    <span class="hljs-keyword">case</span> inverted

    <span class="hljs-keyword">var</span> elementKind: <span class="hljs-type">String</span> { <span class="hljs-string">"background.\(rawValue)"</span> }

    <span class="hljs-keyword">var</span> viewClass: <span class="hljs-type">AnyClass</span> {
        <span class="hljs-keyword">switch</span> <span class="hljs-keyword">self</span> {
        <span class="hljs-keyword">case</span> .inverted: <span class="hljs-keyword">return</span> <span class="hljs-type">InvertedBackgroundView</span>.<span class="hljs-keyword">self</span>
        }
    }
}
</code></pre>
<p>Then we'll add an extension on <code>UICollectionViewLayout</code> which will register all our backgrounds for us, because as far as I can tell, there is currently no way to use a cell registration for decoration items like you can with cells or headers:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Collection View Elements/BackgroundView.swift</span>

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">UICollectionViewLayout</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">registerBackgrounds</span><span class="hljs-params">()</span></span> {
        <span class="hljs-type">BackgroundStyle</span>.allCases.forEach {
            register($<span class="hljs-number">0</span>.viewClass, forDecorationViewOfKind: $<span class="hljs-number">0</span>.elementKind)
        }
    }
}
</code></pre>
<p>And we'll add an extension on <code>NSCollectionLayoutSection</code> which will let us add a background to that section by just passing it what style we want. <em>Two cheers for auto-complete.</em></p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Collection View Elements/BackgroundView.swift</span>

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">NSCollectionLayoutSection</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">addBackground</span><span class="hljs-params">(style: BackgroundStyle)</span></span> {
        decorationItems.append(.background(elementKind: style.elementKind))
    }
}
</code></pre>
<h2 id="using-the-inverted-views">Using the Inverted Views</h2>
<p>Now we need to update our collection view and data source to actually use all the new views we've defined. First lets add a new section that will use the inverted background.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Collection View Elements/NSCollectionLayoutSection+Layouts.swift</span>

<span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">invertedSideScrollingOneItem</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">NSCollectionLayoutSection</span> {
    <span class="hljs-keyword">let</span> section = sideScrollingOneItem()
    section.addBackground(style: .inverted)
    <span class="hljs-keyword">return</span> section
}
</code></pre>
<p>Then lets update the collection view to use it. And don't forget to register the backgrounds or it will crash.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HomeView.swift</span>

<span class="hljs-comment">// in makeCollectionView()</span>
<span class="hljs-keyword">let</span> layout = <span class="hljs-type">UICollectionViewCompositionalLayout</span> { sectionIndex, layoutEnvironment <span class="hljs-keyword">in</span>
    <span class="hljs-keyword">let</span> section = <span class="hljs-type">Section</span>.allCases[sectionIndex]
    <span class="hljs-keyword">switch</span> section {
    <span class="hljs-keyword">case</span> .experiences:
        <span class="hljs-keyword">return</span> .invertedSideScrollingOneItem()
    <span class="hljs-comment">// other cases...</span>
    }
}
layout.registerBackgrounds()
<span class="hljs-keyword">return</span> <span class="hljs-type">UICollectionView</span>(frame: .zero, collectionViewLayout: layout)
</code></pre>
<p>Then we need to update our data source to use the <code>InvertedLargeSquareCell</code> registration for the "experiences" section.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HomeView.swift</span>

<span class="hljs-comment">// in the switch statement in makeDataSource()</span>
<span class="hljs-keyword">case</span> .experiences:
    <span class="hljs-keyword">let</span> registration = <span class="hljs-type">InvertedLargeSquareCell</span>.registration()
    <span class="hljs-keyword">return</span> view.dequeueConfiguredReusableCell(using: registration,
                                              <span class="hljs-keyword">for</span>: indexPath,
                                              item: item)
</code></pre>
<p>And we'll need to update the <code>supplementaryViewProvider</code> for the data source as well, to get the inverted header:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HomeView.swift</span>

<span class="hljs-comment">// at the bottom of makeDataSource()</span>
<span class="hljs-keyword">let</span> headers = <span class="hljs-type">Section</span>.allCases.<span class="hljs-built_in">map</span> { $<span class="hljs-number">0</span>.headerContent }
<span class="hljs-keyword">let</span> headerRegistration = <span class="hljs-type">SectionHeader</span>.registration(headers: headers)
<span class="hljs-keyword">let</span> invertedHeaderRegistration = <span class="hljs-type">InvertedHeader</span>.registration(headers: headers)
dataSource.supplementaryViewProvider = { collectionView, string, indexPath <span class="hljs-keyword">in</span>
    <span class="hljs-keyword">let</span> section = <span class="hljs-type">Section</span>.allCases[indexPath.section]
    <span class="hljs-keyword">switch</span> section {
    <span class="hljs-keyword">case</span> .experiences:
        <span class="hljs-keyword">return</span> collectionView
            .dequeueConfiguredReusableSupplementary(using: invertedHeaderRegistration,
                                                    <span class="hljs-keyword">for</span>: indexPath)
    <span class="hljs-keyword">default</span>:
        <span class="hljs-keyword">return</span> collectionView
            .dequeueConfiguredReusableSupplementary(using: headerRegistration,
                                                    <span class="hljs-keyword">for</span>: indexPath)
    }
}
</code></pre>
<p>And that gets us a beautiful inverted section that works in both dark mode and light mode!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1617837009303/k_ew5P6-6.png" alt="Screen Shot 2021-03-06 at 6.37.59 PM.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1617837019912/YL-SxhGfp.png" alt="Screen Shot 2021-03-06 at 6.38.18 PM.png" /></p>
<h2 id="wrap-up">Wrap Up</h2>
<p>In this part we added some convenience extensions for inverting colors, regardless of wether we're in dark mode or light mode. We added support for inverted versions of our cell and section header types. And we added the ability to have background in our sections. In the next part we'll add the footer which will use a new cell, a new layout and a new background.</p>
<hr />
<p>Check out the code up to this point in <a target="_blank" href="https://github.com/dillon-mce/Airbnb-Home/tree/part-4">this repo</a>.</p>
<div class="hn-embed-widget" id="buyacoffee"></div>]]></content:encoded></item><item><title><![CDATA[Building Airbnb's UI From Scratch - Part 3]]></title><description><![CDATA[In this series of posts I am going to be building out the UI for Airbnb's "Explore" tab from scratch. This is an exercise in figuring out how to achieve the different layouts and effects you find in popular apps and I chose Airbnb because I thought i...]]></description><link>https://dilloncodes.com/airbnb-part-3</link><guid isPermaLink="true">https://dilloncodes.com/airbnb-part-3</guid><category><![CDATA[iOS]]></category><category><![CDATA[Xcode]]></category><category><![CDATA[Swift]]></category><category><![CDATA[UI]]></category><dc:creator><![CDATA[Dillon McElhinney]]></dc:creator><pubDate>Thu, 20 May 2021 13:37:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1618036925710/w-vdzFBf_.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this series of posts I am going to be building out the UI for Airbnb's "Explore" tab from scratch. This is an exercise in figuring out how to achieve the different layouts and effects you find in popular apps and I chose <a target="_blank" href="https://apps.apple.com/us/app/airbnb/id401626263?uo=4">Airbnb</a> because I thought it had several great examples. It is not intended to be used for anything other than for educational purposes and all my code will be available at <a target="_blank" href="https://github.com/dillon-mce/Airbnb-Home">this repo</a> if you want to follow along and build it yourself. A few disclaimers: </p>
<ul>
<li>For all the illustrations in the app I just took screenshots of the actual app and cropped them to size. They are not the full versions of the illustrations and they are not really even formatted correctly for use in an iOS app. I tried to spend as little time as possible on that prep. </li>
<li>We will get the fonts, colors and icons as close as we can with just using the system versions. I am pretty sure the font and icons that Airbnb actually uses would require a license and I don't care that much about making this exact. With the way we organize it, it would be pretty easy to swap in the real ones if you want to take that step yourself.</li>
<li>We will not do any business logic. We will hard code all the data and not handle any user actions. Again, this is not meant to be production code, just an exploration of some UI techniques.</li>
<li>There is a good chance that Airbnb will look different by the time you see this. Honestly, there's a non-zero chance that it will change before I finish writing this, so the live app will probably look different than what you see me build, but the layout/principles should be pretty much the same. <em>(Editorial note: it has already changed before I was able to finish writing this series, but I have a few screenshots of what the app looked like before, and we'll just build up to the spec that I created.)</em></li>
</ul>
<p>Here's what our final product will look like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1618017103718/39dgJOE5X.gif" alt="Design Spec.gif" /></p>
<p>With all that said, let's go.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=ZYZ0AD0gRAk">https://www.youtube.com/watch?v=ZYZ0AD0gRAk</a></div>
<h1 id="filling-out-the-collection-view">Filling Out The Collection View</h1>
<p>We're going to be adding a couple of different cells for this app and even though they will all use the same <code>Content</code> struct to render data on screen, they will use different views to do that. So what I want to do is make the <code>SmallSquareCell</code> wrapper that we wrote last time into a generic <code>ContentCell</code> wrapper that will have <em>some</em> view it will configure with content. So let's add a new file and start by adding a protocol that the view will need to conform to:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Collection View Elements/ContentCell.swift</span>

<span class="hljs-keyword">import</span> Anchorage
<span class="hljs-keyword">import</span> UIKit

<span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">ContentConfiguringView</span>: <span class="hljs-title">UIView</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">(with content: Content?)</span></span>
}
</code></pre>
<p>Then we'll just cut/paste the <code>SmallSquareCell</code> class from last time into this file and make some changes to it:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Collection View Elements/ContentCell.swift</span>

<span class="hljs-comment">// change name and make it generic</span>
<span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ContentCell</span>&lt;<span class="hljs-title">View</span>: <span class="hljs-title">ContentConfiguringView</span>&gt;: <span class="hljs-title">UICollectionViewCell</span> </span>{

    <span class="hljs-comment">// change the view to be View instead of SmallSquareView</span>
    <span class="hljs-keyword">private</span> <span class="hljs-built_in">lazy</span> <span class="hljs-keyword">var</span> view: <span class="hljs-type">View</span> = .<span class="hljs-keyword">init</span>()

    <span class="hljs-comment">// other stuff...</span>

    <span class="hljs-comment">// change registration so it returns its type is &lt;ContentCell&lt;View&gt;, Content&gt;</span>
    <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">registration</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">UICollectionView</span>.<span class="hljs-type">CellRegistration</span>&lt;<span class="hljs-type">ContentCell</span>&lt;<span class="hljs-type">View</span>&gt;, <span class="hljs-type">Content</span>&gt; {
        <span class="hljs-type">UICollectionView</span>.<span class="hljs-type">CellRegistration</span> { cell, indexPath, content <span class="hljs-keyword">in</span>
            cell.configure(with: content)
        }
    }
}
</code></pre>
<p>And with that our cell wrapper is generic! It won't compile yet, because we our view doesn't conform to the protocol and we no longer have a class called <code>SmallSquareCell</code>, so let's fix both of those:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Collection View Elements/SmallSquareCell.swift</span>

<span class="hljs-comment">// add a type alias for convenience</span>
<span class="hljs-keyword">typealias</span> <span class="hljs-type">SmallSquareCell</span> = <span class="hljs-type">ContentCell</span>&lt;<span class="hljs-type">SmallSquareView</span>&gt;

<span class="hljs-comment">// make the view adopt the new protocol,</span>
<span class="hljs-comment">// which it already conforms to</span>
<span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SmallSquareView</span>: <span class="hljs-title">ProgrammaticView</span>, <span class="hljs-title">ContentConfiguringView</span> </span>{
</code></pre>
<p>Now the app should run again and will look the same as before.</p>
<h2 id="second-cell">Second Cell</h2>
<p>Now that we have a generic cell, we can just write our view and toss it in there. It is pretty similar to the <code>SmallSquareView</code>, with a slightly different layout, so I'm not going to go into too much detail. Here's what it looks like:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Collection View Elements/LargeSquareCell.swift</span>

<span class="hljs-keyword">typealias</span> <span class="hljs-type">LargeSquareCell</span> = <span class="hljs-type">ContentCell</span>&lt;<span class="hljs-type">LargeSquareView</span>&gt;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LargeSquareView</span>: <span class="hljs-title">ProgrammaticView</span>, <span class="hljs-title">ContentConfiguringView</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> mainStack = <span class="hljs-type">UIStackView</span>()
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> imageView = <span class="hljs-type">UIImageView</span>()
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> labelStack = <span class="hljs-type">UIStackView</span>()
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> titleLabel = <span class="hljs-type">UILabel</span>()
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> subtitleLabel = <span class="hljs-type">UILabel</span>()

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">()</span></span> {
        mainStack.axis = .vertical
        mainStack.spacing = <span class="hljs-number">10</span>

        imageView.backgroundColor = .secondarySystemFill
        imageView.layer.cornerRadius = <span class="hljs-number">8</span>
        imageView.layer.masksToBounds = <span class="hljs-literal">true</span>

        labelStack.axis = .vertical
        labelStack.spacing = <span class="hljs-number">2</span>

        titleLabel.font = .custom(style: .headline)
        titleLabel.textColor = .label
        subtitleLabel.font = .custom(style: .subheadline)
        subtitleLabel.textColor = .label
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">constrain</span><span class="hljs-params">()</span></span> {
        addSubviews(mainStack)
        mainStack.addArrangedSubviews(imageView, labelStack)
        labelStack.addArrangedSubviews(titleLabel, subtitleLabel)

        mainStack.edgeAnchors == edgeAnchors

        imageView.widthAnchor == imageView.heightAnchor
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">(with content: Content?)</span></span> {
        titleLabel.text = content?.title
        subtitleLabel.text = content?.subtitle
        imageView.image = <span class="hljs-type">UIImage</span>(named: content?.image)
    }
}
</code></pre>
<h2 id="second-section">Second Section</h2>
<p>We'll also need a new layout for this new cell type. Again, it is basically the same as the last one just with only one item per group this time.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Collection View Elements/NSCollectionLayoutSection+Layouts.swift</span>

<span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">sideScrollingOneItem</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">NSCollectionLayoutSection</span> {
    <span class="hljs-keyword">let</span> itemSize = <span class="hljs-type">NSCollectionLayoutSize</span>(widthDimension: .fractionalWidth(<span class="hljs-number">1</span>),
                                          heightDimension: .estimated(<span class="hljs-number">312</span>))
    <span class="hljs-keyword">let</span> item = <span class="hljs-type">NSCollectionLayoutItem</span>(layoutSize: itemSize)

    <span class="hljs-keyword">let</span> groupSize = <span class="hljs-type">NSCollectionLayoutSize</span>(widthDimension: .fractionalWidth(<span class="hljs-number">0.7</span>),
                                           heightDimension: .estimated(<span class="hljs-number">312</span>))
    <span class="hljs-keyword">let</span> group = <span class="hljs-type">NSCollectionLayoutGroup</span>.horizontal(layoutSize: groupSize,
                                                 subitems: [item])

    <span class="hljs-keyword">let</span> section = <span class="hljs-type">NSCollectionLayoutSection</span>(group: group)
    section.orthogonalScrollingBehavior = .groupPaging
    section.interGroupSpacing = <span class="hljs-number">12</span>
    section.contentInsets = .<span class="hljs-keyword">init</span>(top: <span class="hljs-number">20</span>, leading: <span class="hljs-number">20</span>, bottom: <span class="hljs-number">20</span>, trailing: <span class="hljs-number">20</span>)
    <span class="hljs-keyword">return</span> section
}
</code></pre>
<h2 id="using-the-new-stuff">Using The New Stuff</h2>
<p>Now it is time to uncomment all of the remaining sections except the last one. We'll also need to uncomment the related stub data:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Models/Content.swift</span>

<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">Section</span>: <span class="hljs-title">Int</span>, <span class="hljs-title">Hashable</span>, <span class="hljs-title">CaseIterable</span> </span>{
    <span class="hljs-keyword">case</span> nearby, stays, experiences, hosting<span class="hljs-comment">//, info</span>
}

<span class="hljs-comment">// in stubData()</span>
<span class="hljs-keyword">case</span> .stays:
    <span class="hljs-keyword">return</span> [
        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Entire homes"</span>, subtitle: <span class="hljs-literal">nil</span>, image: <span class="hljs-string">"entire-homes"</span>),
        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Cabins and cottages"</span>, subtitle: <span class="hljs-literal">nil</span>, image: <span class="hljs-string">"cabins-cottages"</span>),
        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Unique stays"</span>, subtitle: <span class="hljs-literal">nil</span>, image: <span class="hljs-string">"unique-stays"</span>),
        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Pets welcome"</span>, subtitle: <span class="hljs-literal">nil</span>, image: <span class="hljs-string">"pets-welcome"</span>),
    ]
<span class="hljs-keyword">case</span> .experiences:
    <span class="hljs-keyword">return</span> [
        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Online Experiences"</span>,
              subtitle: <span class="hljs-string">"Travel the world without leaving home."</span>,
              image: <span class="hljs-string">"online-experiences"</span>),
        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Experiences"</span>,
              subtitle: <span class="hljs-string">"Things to do wherever you are."</span>,
              image: <span class="hljs-string">"experiences"</span>),
        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Adventures"</span>,
              subtitle: <span class="hljs-string">"Multi-day trips with meals and stays."</span>,
              image: <span class="hljs-string">"adventures"</span>),
    ]
<span class="hljs-keyword">case</span> .hosting:
    <span class="hljs-keyword">return</span> [
        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Host your home"</span>,
              subtitle: <span class="hljs-literal">nil</span>,
              image: <span class="hljs-string">"host-your-home"</span>),
        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Host an Online Experience"</span>,
              subtitle: <span class="hljs-literal">nil</span>,
              image: <span class="hljs-string">"host-online-experience"</span>),
        .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Host an Experience"</span>,
              subtitle: <span class="hljs-literal">nil</span>,
              image: <span class="hljs-string">"host-experience"</span>),
    ]
</code></pre>
<p>Then we'll also need to update  <code>makeDataSource</code> and <code>makeCollectionView</code> to account for those new sections:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HomeView.swift</span>

<span class="hljs-comment">// in the switch statement in makeCollectionView()</span>
<span class="hljs-keyword">default</span>:
    <span class="hljs-keyword">return</span> .sideScrollingOneItem()

<span class="hljs-comment">// in the switch statement in makeDataSource()</span>
<span class="hljs-keyword">default</span>:
    <span class="hljs-keyword">let</span> registration = <span class="hljs-type">LargeSquareCell</span>.registration()
    <span class="hljs-keyword">return</span> view.dequeueConfiguredReusableCell(using: registration,
                                              <span class="hljs-keyword">for</span>: indexPath,
                                              item: item)
</code></pre>
<p>Now, if you run the app, you'll see three new sections full of content!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1617835556195/tODWmAWFc.png" alt="Screen Shot 2021-03-02 at 7.29.34 PM.png" /></p>
<h2 id="adding-headers">Adding Headers</h2>
<p>If we look at the spec we're going off of, we'll find that these subsequent sections all have a header. In collection views we add those with a <code>UICollectionReusableView</code>. To keep things consistent, I'm going to follow the same pattern we used with the cells, making a generic wrapper for that which holds a view where we will define the actual layout.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Collection View Elements/SectionHeader.swift</span>

<span class="hljs-keyword">import</span> Anchorage
<span class="hljs-keyword">import</span> UIKit

<span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">ContentConfiguringHeader</span>: <span class="hljs-title">UIView</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">(with content: Content?)</span></span>
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ContentHeader</span>&lt;<span class="hljs-title">View</span>: <span class="hljs-title">ContentConfiguringHeader</span>&gt;: <span class="hljs-title">UICollectionReusableView</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-built_in">lazy</span> <span class="hljs-keyword">var</span> view: <span class="hljs-type">View</span> = .<span class="hljs-keyword">init</span>()

    <span class="hljs-keyword">override</span> <span class="hljs-keyword">init</span>(frame: <span class="hljs-type">CGRect</span>) {
        <span class="hljs-keyword">super</span>.<span class="hljs-keyword">init</span>(frame: frame)
        constrain()
    }

    <span class="hljs-keyword">required</span> <span class="hljs-keyword">init</span>?(coder: <span class="hljs-type">NSCoder</span>) {
        <span class="hljs-keyword">super</span>.<span class="hljs-keyword">init</span>(coder: coder)
        constrain()
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">constrain</span><span class="hljs-params">()</span></span> {
        addSubview(view)
        view.edgeAnchors == layoutMarginsGuide.edgeAnchors
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">(with content: Content?)</span></span> {
        view.configure(with: content)
    }

    <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">registration</span><span class="hljs-params">(headers: [Content?])</span></span> -&gt; <span class="hljs-type">UICollectionView</span>.<span class="hljs-type">SupplementaryRegistration</span>&lt;<span class="hljs-type">ContentHeader</span>&lt;<span class="hljs-type">View</span>&gt;&gt; {
        <span class="hljs-type">UICollectionView</span>.<span class="hljs-type">SupplementaryRegistration</span>(kind: .header) { header, string, indexPath <span class="hljs-keyword">in</span>
            <span class="hljs-keyword">let</span> content = headers[indexPath.section]
            header.configure(with: content)
        }
    }
}
</code></pre>
<p>The supplementary registrations use a <code>String</code> to keep track of the element kind, and I try to keep things a little more tightly reined than that, so I'll add an enum which will have a case for each kind of supplementary view that my app will use, and I'll add a convenience initializer for <code>UICollectionView.SupplementaryRegistration</code> which takes that enum and passes it on as a <code>String</code>.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Collection View Elements/SectionHeader.swift</span>

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">UICollectionView</span> </span>{
    <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">ElementKind</span>: <span class="hljs-title">String</span> </span>{
        <span class="hljs-keyword">case</span> header
    }
}

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">UICollectionView</span>.<span class="hljs-title">SupplementaryRegistration</span> </span>{
    <span class="hljs-keyword">init</span>(kind: <span class="hljs-type">UICollectionView</span>.<span class="hljs-type">ElementKind</span>,
         handler: @escaping <span class="hljs-type">UICollectionView</span>.<span class="hljs-type">SupplementaryRegistration</span>&lt;<span class="hljs-type">Supplementary</span>&gt;.<span class="hljs-type">Handler</span>) {
        <span class="hljs-keyword">self</span>.<span class="hljs-keyword">init</span>(elementKind: kind.rawValue, handler: handler)
    }
}
</code></pre>
<p>Then we need to add the view:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Collection View Elements/SectionHeader.swift</span>

<span class="hljs-keyword">typealias</span> <span class="hljs-type">SectionHeader</span> = <span class="hljs-type">ContentHeader</span>&lt;<span class="hljs-type">SectionHeaderView</span>&gt;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SectionHeaderView</span>: <span class="hljs-title">ProgrammaticView</span>, <span class="hljs-title">ContentConfiguringHeader</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> titleLabel = <span class="hljs-type">UILabel</span>()
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> subtitleLabel = <span class="hljs-type">UILabel</span>()

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">()</span></span> {
        directionalLayoutMargins = .<span class="hljs-keyword">init</span>(top: <span class="hljs-number">24</span>, leading: <span class="hljs-number">0</span>, bottom: <span class="hljs-number">0</span>, trailing: <span class="hljs-number">0</span>)
        titleLabel.font = .custom(style: .title2)
        titleLabel.adjustsFontForContentSizeCategory = <span class="hljs-literal">true</span>
        titleLabel.numberOfLines = <span class="hljs-number">0</span>
        titleLabel.textColor = .label
        subtitleLabel.font = .custom(style: .title4)
        subtitleLabel.numberOfLines = <span class="hljs-number">0</span>
        subtitleLabel.textColor = .label
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">constrain</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">let</span> stackView = <span class="hljs-type">UIStackView</span>(arrangedSubviews: [titleLabel, subtitleLabel])
        stackView.axis = .vertical
        stackView.spacing = <span class="hljs-number">4</span>

        addSubview(stackView)
        stackView.edgeAnchors == layoutMarginsGuide.edgeAnchors
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">(with content: Content?)</span></span> {
        titleLabel.text = content?.title
        subtitleLabel.text = content?.subtitle
    }
}
</code></pre>
<p>You'll notice that we're using a couple new font styles in this view, so we need ot add those as well.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Extensions/UIFont+Custom.swift</span>

<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">var</span> title2: <span class="hljs-type">UIFont</span> {
    <span class="hljs-type">UIFontMetrics</span>(forTextStyle: .title2)
        .scaledFont(<span class="hljs-keyword">for</span>: .systemFont(ofSize: <span class="hljs-number">18</span>, weight: .bold))
}

<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">var</span> title4: <span class="hljs-type">UIFont</span> {
    <span class="hljs-type">UIFontMetrics</span>(forTextStyle: .title3)
        .scaledFont(<span class="hljs-keyword">for</span>: .systemFont(ofSize: <span class="hljs-number">15</span>, weight: .light))
}

<span class="hljs-comment">// in Style</span>
<span class="hljs-keyword">case</span> title2
<span class="hljs-keyword">case</span> title4

<span class="hljs-comment">// in switch statement in custom(style:)</span>
<span class="hljs-keyword">case</span> .title2: <span class="hljs-keyword">return</span> title2
<span class="hljs-keyword">case</span> .title4: <span class="hljs-keyword">return</span> title4
</code></pre>
<h2 id="using-the-headers">Using The Headers</h2>
<p>Now we need to content for the headers. Let's uncomment the <code>headerContent</code> property on <code>Section</code>:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Models/Content.swift</span>

<span class="hljs-keyword">var</span> headerContent: <span class="hljs-type">Content?</span> {
    <span class="hljs-keyword">switch</span> <span class="hljs-keyword">self</span> {
    <span class="hljs-keyword">case</span> .nearby: <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
    <span class="hljs-keyword">case</span> .stays: <span class="hljs-keyword">return</span> .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Live anywhere"</span>, subtitle: <span class="hljs-literal">nil</span>, image: <span class="hljs-literal">nil</span>)
    <span class="hljs-keyword">case</span> .experiences: <span class="hljs-keyword">return</span> .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Experience the world"</span>,
                                    subtitle: <span class="hljs-string">"Unique activities with local experts—in person or online."</span>,
                                    image: <span class="hljs-literal">nil</span>)
    <span class="hljs-keyword">case</span> .hosting: <span class="hljs-keyword">return</span> .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Join millions of hosts on Airbnb"</span>, subtitle: <span class="hljs-literal">nil</span>, image: <span class="hljs-literal">nil</span>)
<span class="hljs-comment">//        case .info: return .init(title: "Stay informed", subtitle: nil, image: nil)</span>
    }
}
</code></pre>
<p>Then we need to update <code>makeDataSource</code> to take that header content and stick it in header views.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HomeView.swift</span>

<span class="hljs-comment">// at the end of makeDataSource()</span>
<span class="hljs-keyword">let</span> headers = <span class="hljs-type">Section</span>.allCases.<span class="hljs-built_in">map</span> { $<span class="hljs-number">0</span>.headerContent }
<span class="hljs-keyword">let</span> headerRegistration = <span class="hljs-type">SectionHeader</span>.registration(headers: headers)
dataSource.supplementaryViewProvider = { collectionView, string, indexPath <span class="hljs-keyword">in</span>
    collectionView.dequeueConfiguredReusableSupplementary(using: headerRegistration,
                                                          <span class="hljs-keyword">for</span>: indexPath)
}
<span class="hljs-keyword">return</span> dataSource
</code></pre>
<p>Finally, we'll add another couple of helpers that set some default values for us and let us use element types that are constrained by an enum. Then we just need to add the layout information to our section definition.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In SectionHeader.swift</span>

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">NSCollectionLayoutBoundarySupplementaryItem</span> </span>{
    <span class="hljs-keyword">convenience</span> <span class="hljs-keyword">init</span>(layoutSize: <span class="hljs-type">NSCollectionLayoutSize</span>,
                     kind: <span class="hljs-type">UICollectionView</span>.<span class="hljs-type">ElementKind</span>,
                     alignment: <span class="hljs-type">NSRectAlignment</span>) {
        <span class="hljs-keyword">self</span>.<span class="hljs-keyword">init</span>(layoutSize: layoutSize,
                  elementKind: kind.rawValue,
                  alignment: alignment)
    }

    <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">header</span><span class="hljs-params">(layoutSize: NSCollectionLayoutSize)</span></span> -&gt;
    <span class="hljs-type">NSCollectionLayoutBoundarySupplementaryItem</span> {
        .<span class="hljs-keyword">init</span>(layoutSize: layoutSize, kind: .header, alignment: .top)
    }
}

<span class="hljs-comment">// In Collection View Elements/NSCollectionLayoutSection+Layouts.swift</span>

<span class="hljs-comment">// in sideScrollingOneItem()</span>
<span class="hljs-keyword">let</span> headerSize = <span class="hljs-type">NSCollectionLayoutSize</span>(widthDimension: .fractionalWidth(<span class="hljs-number">1</span>),
                                        heightDimension: .estimated(<span class="hljs-number">100</span>))
<span class="hljs-keyword">let</span> header = <span class="hljs-type">NSCollectionLayoutBoundarySupplementaryItem</span>.header(layoutSize: headerSize)

<span class="hljs-keyword">let</span> section = <span class="hljs-type">NSCollectionLayoutSection</span>(group: group)
section.boundarySupplementaryItems = [header]
</code></pre>
<p>And with that, you can run the app and we'll see headers in the sections that have them!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1617835877275/N8HspX_7u.png" alt="Screen Shot 2021-03-04 at 8.41.28 AM.png" /></p>
<h2 id="wrap-up">Wrap Up</h2>
<p>In this part we really took advantage of the groundwork we made in the last part. We made our cell wrapper more reusable by making it generic. We defined a new cell layout and used it with the next several sections of content. And we added headers for the sections that have them. In the next part we'll take a look at adding backgrounds and dark mode support.</p>
<hr />
<p>Check out the code up to this point in <a target="_blank" href="https://github.com/dillon-mce/Airbnb-Home/tree/part-3">this repo</a>.</p>
<div class="hn-embed-widget" id="buyacoffee"></div>]]></content:encoded></item><item><title><![CDATA[Building Airbnb's UI From Scratch - Part 2]]></title><description><![CDATA[In this series of posts I am going to be building out the UI for Airbnb's "Explore" tab from scratch. This is an exercise in figuring out how to achieve the different layouts and effects you find in popular apps and I chose Airbnb because I thought i...]]></description><link>https://dilloncodes.com/airbnb-part-2</link><guid isPermaLink="true">https://dilloncodes.com/airbnb-part-2</guid><category><![CDATA[iOS]]></category><category><![CDATA[Xcode]]></category><category><![CDATA[Swift]]></category><category><![CDATA[UI]]></category><dc:creator><![CDATA[Dillon McElhinney]]></dc:creator><pubDate>Tue, 04 May 2021 15:33:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1618036920367/ZWg6wOROO.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this series of posts I am going to be building out the UI for Airbnb's "Explore" tab from scratch. This is an exercise in figuring out how to achieve the different layouts and effects you find in popular apps and I chose <a target="_blank" href="https://apps.apple.com/us/app/airbnb/id401626263?uo=4">Airbnb</a> because I thought it had several great examples. It is not intended to be used for anything other than for educational purposes and all my code will be available at <a target="_blank" href="https://github.com/dillon-mce/Airbnb-Home">this repo</a> if you want to follow along and build it yourself. A few disclaimers: </p>
<ul>
<li>For all the illustrations in the app I just took screenshots of the actual app and cropped them to size. They are not the full versions of the illustrations and they are not really even formatted correctly for use in an iOS app. I tried to spend as little time as possible on that prep. </li>
<li>We will get the fonts, colors and icons as close as we can with just using the system versions. I am pretty sure the font and icons that Airbnb actually uses would require a license and I don't care that much about making this exact. With the way we organize it, it would be pretty easy to swap in the real ones if you want to take that step yourself.</li>
<li>We will not do any business logic. We will hard code all the data and not handle any user actions. Again, this is not meant to be production code, just an exploration of some UI techniques.</li>
<li>There is a good chance that Airbnb will look different by the time you see this. Honestly, there's a non-zero chance that it will change before I finish writing this, so the live app will probably look different than what you see me build, but the layout/principles should be pretty much the same. <em>(Editorial note: it has already changed before I was able to finish writing this series, but I have a few screenshots of what the app looked like before, and we'll just build up to the spec that I created.)</em></li>
</ul>
<p>Here's what our final product will look like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1618017103718/39dgJOE5X.gif" alt="Design Spec.gif" /></p>
<p>With all that said, let's go.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/58E3SUvYNRs">https://youtu.be/58E3SUvYNRs</a></div>
<h1 id="first-cell-and-section">First Cell and Section</h1>
<p>The first thing we need to do is lay out the home view. It will be separated into basically two parts, the header and the collection view, so let's add those:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HomeView.swift</span>

<span class="hljs-comment">// add views, just using an empty collection view for now</span>
<span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> headerView = <span class="hljs-type">HeaderView</span>()
<span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> collectionView = <span class="hljs-type">UICollectionView</span>(frame: .zero,
                                              collectionViewLayout: .<span class="hljs-keyword">init</span>())

<span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">()</span></span> {
     <span class="hljs-comment">// change the collection view's background color so we know where it is</span>
    collectionView.backgroundColor = .systemPink
}

<span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">constrain</span><span class="hljs-params">()</span></span> {
    <span class="hljs-comment">// use the helper to add the subviews</span>
    addSubviews(headerView, collectionView)

    <span class="hljs-comment">// header should be tied to the top,</span>
    <span class="hljs-comment">// leading and trailing edges of the view</span>
    <span class="hljs-comment">// and the bottom to the top of the collection view</span>
    headerView.topAnchor == topAnchor
    headerView.horizontalAnchors == horizontalAnchors
    headerView.bottomAnchor == collectionView.topAnchor

    <span class="hljs-comment">// collection view is constrained to take</span>
    <span class="hljs-comment">// the rest of the available space</span>
    collectionView.horizontalAnchors == horizontalAnchors
    collectionView.bottomAnchor == bottomAnchor
}
</code></pre>
<p>If you run the app right now, you'll find that the whole screen is teal, which is the header's color. This is because we aren't constraining it's height anywhere, so it taking up the whole view is a valid layout according to the constraints we have. For this project, we're going to have the header manage it's own height so we can encapsulate all the animation stuff into that one view. For now, let's just give it a static height.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HeaderView.swift</span>

<span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">constrain</span><span class="hljs-params">()</span></span> {
    heightAnchor == <span class="hljs-number">100</span>
}
</code></pre>
<p>Now if we run it, it looks roughly the same proportions as Airbnb when the header is in the fully-collapsed state and that's what we want for now.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1616979729102/3rrtfJMZf.png" alt="Screen Shot 2021-03-02 at 6.39.43 PM.png" /></p>
<h2 id="add-the-smallsquareview">Add The SmallSquareView</h2>
<p>To make things easier for ourselves, we're going to define our cells' views as <code>ProgrammaticView</code>s and just use the <code>UICollectionViewCell</code> as a wrapper around that. So add a file called <code>SmallSquareCell.swift</code> and let's write the view:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Collection View Elements/SmallSquareCell.swift</span>

<span class="hljs-keyword">import</span> Anchorage
<span class="hljs-keyword">import</span> UIKit

<span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SmallSquareView</span>: <span class="hljs-title">ProgrammaticView</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> imageView = <span class="hljs-type">UIImageView</span>()
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> stack = <span class="hljs-type">UIStackView</span>()
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> titleLabel = <span class="hljs-type">UILabel</span>()
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> subtitleLabel = <span class="hljs-type">UILabel</span>()

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">()</span></span> {
        imageView.backgroundColor = .secondarySystemFill
        imageView.layer.cornerRadius = <span class="hljs-number">8</span>
        imageView.layer.masksToBounds = <span class="hljs-literal">true</span>

        stack.axis = .vertical
        stack.spacing = <span class="hljs-number">8</span>
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">constrain</span><span class="hljs-params">()</span></span> {
        addSubviews(imageView, stack)
        stack.addArrangedSubviews(titleLabel, subtitleLabel)

        imageView.verticalAnchors == verticalAnchors
        imageView.leadingAnchor == leadingAnchor
        imageView.widthAnchor == imageView.heightAnchor

        stack.leadingAnchor == imageView.trailingAnchor + <span class="hljs-number">10</span>
        stack.trailingAnchor == trailingAnchor
        stack.centerYAnchor == centerYAnchor
    }
}
</code></pre>
<p>That gets us most of the way there, but we need two more things. We need to set the fonts of the labels, and we need a way to get the content into this view. For the fonts, I like to define semantically named static constants on <code>UIFont</code>, whose names roughly match the ones that Apple provides, but whose style matches my app. I also like to define a function called <code>custom(style:)</code> that returns one of those styles to make it clear in code that this is our app's font, not a system font. That looks like this:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Extensions/UIFont+Custom.swift</span>

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">UIFont</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">var</span> headline: <span class="hljs-type">UIFont</span> {
        <span class="hljs-type">UIFontMetrics</span>(forTextStyle: .headline)
            .scaledFont(<span class="hljs-keyword">for</span>: .systemFont(ofSize: <span class="hljs-number">14</span>, weight: .semibold))
    }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">var</span> subheadline: <span class="hljs-type">UIFont</span> {
        <span class="hljs-type">UIFontMetrics</span>(forTextStyle: .subheadline)
            .scaledFont(<span class="hljs-keyword">for</span>: .systemFont(ofSize: <span class="hljs-number">12</span>, weight: .light))
    }
}

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">UIFont</span> </span>{
    <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">Style</span> </span>{
        <span class="hljs-keyword">case</span> headline
        <span class="hljs-keyword">case</span> subheadline
    }

    <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">custom</span><span class="hljs-params">(style: Style)</span></span> -&gt; <span class="hljs-type">UIFont</span> {
        <span class="hljs-keyword">switch</span> style {
        <span class="hljs-keyword">case</span> .headline: <span class="hljs-keyword">return</span> headline
        <span class="hljs-keyword">case</span> .subheadline: <span class="hljs-keyword">return</span> subheadline
        }
    }
}
</code></pre>
<p>This gives us a central place to define the fonts for our app, it builds in support for dynamically-sized fonts and it gives us a clear and consistent api to use at the call site:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In SmallSquareView.configure</span>

titleLabel.font = .custom(style: .headline)
subtitleLabel.font = .custom(style: .subheadline)
</code></pre>
<p>We also need a way to give this view some <code>Content</code> that it will then display. We're going to do that with this function:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Collection View Elements/SmallSquareCell.swift</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">(with content: Content?)</span></span> {
    titleLabel.text = content?.title
    subtitleLabel.text = content?.subtitle
    imageView.image = <span class="hljs-type">UIImage</span>(named: content?.image) <span class="hljs-comment">// using our UIImage initializer</span>
}
</code></pre>
<h3 id="our-first-cell">Our First Cell</h3>
<p>Now we need to use that view in a collection view cell, so it can actually be displayed. I try to keep this as simple as possible, just a straight wrapper that passes things through to the view.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SmallSquareCell</span>: <span class="hljs-title">UICollectionViewCell</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-built_in">lazy</span> <span class="hljs-keyword">var</span> view: <span class="hljs-type">SmallSquareView</span> = .<span class="hljs-keyword">init</span>()

    <span class="hljs-keyword">override</span> <span class="hljs-keyword">init</span>(frame: <span class="hljs-type">CGRect</span>) {
        <span class="hljs-keyword">super</span>.<span class="hljs-keyword">init</span>(frame: frame)
        constrain()
    }

    <span class="hljs-keyword">required</span> <span class="hljs-keyword">init</span>?(coder: <span class="hljs-type">NSCoder</span>) {
        <span class="hljs-keyword">super</span>.<span class="hljs-keyword">init</span>(coder: coder)
        constrain()
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">constrain</span><span class="hljs-params">()</span></span> {
        contentView.addSubview(view)
        view.edgeAnchors == contentView.edgeAnchors
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">(with content: Content?)</span></span> {
        view.configure(with: content)
    }
}
</code></pre>
<p>Now that we have a full-fledged cell that we can use, but we need a layout and a data source for the collection view. </p>
<h2 id="our-first-section">Our First Section</h2>
<p>We're going to use the relatively new <code>UICollectionViewCompositionalLayout</code> for our collection view, which will be composed of several sections. To make things easier to read and reason about, I am going to split up the section definitions into their own functions, which also conveniently makes them resuable. This first one needs to be a side-scrolling section with groups of two items stacked on top of each other, so I'm going to call it <code>sideScrollingTwoItem()</code>.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Collection View Elements/NSCollectionLayoutSection+Layouts.swift</span>

<span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">sideScrollingTwoItem</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">NSCollectionLayoutSection</span> {
    <span class="hljs-comment">// Define the individual items:</span>
    <span class="hljs-comment">// We want the items to be the same width as their group and</span>
    <span class="hljs-comment">// to take as much height as they need, which could change</span>
    <span class="hljs-comment">// because we support dynamically sized fonts</span>
    <span class="hljs-keyword">let</span> itemSize = <span class="hljs-type">NSCollectionLayoutSize</span>(widthDimension: .fractionalWidth(<span class="hljs-number">1</span>),
                                          heightDimension: .estimated(<span class="hljs-number">85</span>))
    <span class="hljs-keyword">let</span> item = <span class="hljs-type">NSCollectionLayoutItem</span>(layoutSize: itemSize)
    item.contentInsets = .<span class="hljs-keyword">init</span>(top: <span class="hljs-number">8</span>, leading: <span class="hljs-number">0</span>, bottom: <span class="hljs-number">8</span>, trailing: <span class="hljs-number">0</span>)

    <span class="hljs-comment">// Define the groups:</span>
    <span class="hljs-comment">// We want the next group to be poking in from the edge</span>
    <span class="hljs-comment">// of the screen, so we'll make the groups a little bit </span>
    <span class="hljs-comment">// less than the full width of the section.</span>
    <span class="hljs-comment">// We also want 2 items per group.</span>
    <span class="hljs-keyword">let</span> groupSize = <span class="hljs-type">NSCollectionLayoutSize</span>(widthDimension: .fractionalWidth(<span class="hljs-number">0.65</span>),
                                           heightDimension: .estimated(<span class="hljs-number">180</span>))
    <span class="hljs-keyword">let</span> group = <span class="hljs-type">NSCollectionLayoutGroup</span>.vertical(layoutSize: groupSize,
                                                 subitem: item,
                                                 <span class="hljs-built_in">count</span>: <span class="hljs-number">2</span>)
    <span class="hljs-comment">// Define the section:</span>
    <span class="hljs-comment">// We want each group to snap into place as you scroll so </span>
    <span class="hljs-comment">// we'll use the group paging orthogonal scrolling behavior.</span>
    <span class="hljs-keyword">let</span> section = <span class="hljs-type">NSCollectionLayoutSection</span>(group: group)
    section.orthogonalScrollingBehavior = .groupPaging
    section.interGroupSpacing = <span class="hljs-number">12</span>
    section.contentInsets = .<span class="hljs-keyword">init</span>(top: <span class="hljs-number">20</span>, leading: <span class="hljs-number">20</span>, bottom: <span class="hljs-number">20</span>, trailing: <span class="hljs-number">20</span>)
    <span class="hljs-keyword">return</span> section
}
</code></pre>
<h2 id="the-section-model">The Section Model</h2>
<p>Now we need some content to show in this section so we are going to add a new type called <code>Section</code> that will define the content for the different sections on our screen. I'll add them all now, but comment out all but the first.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Models/Content.swift</span>

<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">Section</span>: <span class="hljs-title">Int</span>, <span class="hljs-title">Hashable</span>, <span class="hljs-title">CaseIterable</span> </span>{
    <span class="hljs-keyword">case</span> nearby <span class="hljs-comment">//, stays, experiences, hosting, info</span>
}
</code></pre>
<p>While we're here, we can also uncomment the stub data for the first section:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In Models/Content.swift</span>

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">Section</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">stubData</span><span class="hljs-params">()</span></span> -&gt; [<span class="hljs-type">Content</span>] {
        <span class="hljs-keyword">switch</span> <span class="hljs-keyword">self</span> {
        <span class="hljs-keyword">case</span> .nearby:
            <span class="hljs-keyword">return</span> [
                .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Estes Park"</span>, subtitle: <span class="hljs-string">"1.5 hour drive"</span>, image: <span class="hljs-string">"estes-park"</span>),
                .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Breckenridge"</span>, subtitle: <span class="hljs-string">"2.5 hour drive"</span>, image: <span class="hljs-string">"breckenridge"</span>),
                .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Grand Lake"</span>, subtitle: <span class="hljs-string">"3 hour drive"</span>, image: <span class="hljs-string">"grand-lake"</span>),
                .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Idaho Springs"</span>, subtitle: <span class="hljs-string">"2 hour drive"</span>, image: <span class="hljs-string">"idaho-springs"</span>),
                .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Glenwood Springs"</span>, subtitle: <span class="hljs-string">"4.5 hour drive"</span>, image: <span class="hljs-string">"glenwood-springs"</span>),
                .<span class="hljs-keyword">init</span>(title: <span class="hljs-string">"Pagosa Springs"</span>, subtitle: <span class="hljs-string">"7.5 hour drive"</span>, image: <span class="hljs-string">"pagosa-springs"</span>),
            ]
        <span class="hljs-comment">// other cases commented out...</span>
        }
    }
}
</code></pre>
<h2 id="making-the-collection-view">Making The Collection View</h2>
<p>Now that we have a section layout defined, and a data model in place, we can build our collection view. It is really pretty simple, <code>UICollectionViewCompsitionalLayout</code> has an initializer that takes a closure where we are given a section index and a layout environment and we need to return a <code>NSCollectionLayoutSection</code>. Since we defined our layout as a static function on <code>NSCollectionLayoutSection</code>, we can just pick the layout using dot syntax. It looks like this:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In an extension on HomeView</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">makeCollectionView</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">UICollectionView</span> {
    <span class="hljs-keyword">let</span> layout = <span class="hljs-type">UICollectionViewCompositionalLayout</span> { sectionIndex, layoutEnvironment <span class="hljs-keyword">in</span>
        <span class="hljs-keyword">let</span> section = <span class="hljs-type">Section</span>.allCases[sectionIndex]
        <span class="hljs-keyword">switch</span> section {
        <span class="hljs-keyword">case</span> .nearby:
            <span class="hljs-keyword">return</span> .sideScrollingTwoItem()
        }
    }
    <span class="hljs-keyword">return</span> <span class="hljs-type">UICollectionView</span>(frame: .zero, collectionViewLayout: layout)
}
</code></pre>
<p>We don't really need the switch statment right now, since there is only one case. But it will set us up well for adding the rest of the sections in subsequent articles. Now we just need to use this function to initialize our collection view:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HomeView</span>

<span class="hljs-keyword">private</span> <span class="hljs-built_in">lazy</span> <span class="hljs-keyword">var</span> collectionView = makeCollectionView()
</code></pre>
<h2 id="making-the-data-source">Making The Data Source</h2>
<p>Finally, we'll need a data source to provide the content for the collection view. We're going to write another function that will create a <code>UICollectionViewDiffableDataSource</code>, and this is going to take the nested closures thing to a whole other level. The data source is initialized with a collection view and a closure that is given a collection view and an index path and needs to return a configured cell. To configure the cell we're going to use a <code>UICollectionView.CellRegistration</code>, and its initializer <em>also</em> takes a closure which is given a cell, an index path and some content. It can take some time to wrap your head around how all this is working, especially if you're used to the old <code>UICollectionViewDataSource</code> methods. It definitely took me some trial and error. Here's what it will look like:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In an extension on HomeView</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">makeDataSource</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">UICollectionViewDiffableDataSource</span>&lt;<span class="hljs-type">Section</span>, <span class="hljs-type">Content</span>&gt; {
    <span class="hljs-keyword">let</span> registration = <span class="hljs-type">UICollectionView</span>.<span class="hljs-type">CellRegistration</span>&lt;<span class="hljs-type">SmallSquareCell</span>, <span class="hljs-type">Content</span>&gt; {
    cell, indexPath, content <span class="hljs-keyword">in</span>
        cell.configure(with: content)
    }
    <span class="hljs-keyword">let</span> dataSource = <span class="hljs-type">UICollectionViewDiffableDataSource</span>&lt;<span class="hljs-type">Section</span>, <span class="hljs-type">Content</span>&gt;(
        collectionView: collectionView) { view, indexPath, item <span class="hljs-keyword">in</span>
        <span class="hljs-keyword">let</span> section = <span class="hljs-type">Section</span>.allCases[indexPath.section]
        <span class="hljs-keyword">switch</span> section {
        <span class="hljs-keyword">case</span> .nearby:
            <span class="hljs-keyword">return</span> view.dequeueConfiguredReusableCell(using: registration,
                                                      <span class="hljs-keyword">for</span>: indexPath,
                                                      item: item)
        }
    }
    <span class="hljs-keyword">return</span> dataSource
}
</code></pre>
<p>One thing I like to do is define those registrations on the cell class itself, because that gives a convenient place to put it and it is nice and readable at the call site. So let's add this static function on <code>SmallSquareCell</code>:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In SmallSquareCell.swift</span>

<span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">registration</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">UICollectionView</span>.<span class="hljs-type">CellRegistration</span>&lt;<span class="hljs-type">SmallSquareCell</span>, <span class="hljs-type">Content</span>&gt; {
    <span class="hljs-type">UICollectionView</span>.<span class="hljs-type">CellRegistration</span> { cell, indexPath, content <span class="hljs-keyword">in</span>
        cell.configure(with: content)
    }
}
</code></pre>
<p>Then, we can just do this in the <code>.nearby</code> section in <code>makeDataSource</code>:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> registration = <span class="hljs-type">SmallSquareCell</span>.registration()
<span class="hljs-keyword">return</span> view.dequeueConfiguredReusableCell(using: registration,
                                          <span class="hljs-keyword">for</span>: indexPath,
                                          item: item)
</code></pre>
<p>Now we need to use <code>makeDataSource()</code> and set our collection view's data source:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HomeView</span>

<span class="hljs-keyword">private</span> <span class="hljs-built_in">lazy</span> <span class="hljs-keyword">var</span> dataSouce = makeDataSource()

<span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">()</span></span> {
    collectionView.backgroundColor = .systemBackground
    collectionView.dataSource = dataSouce
}
</code></pre>
<h2 id="hooking-up-the-data-source">Hooking Up The Data Source</h2>
<p>Finally, we just need to give the actual data to the data source. For that, we're going to do two things. We'll expose a function on <code>HomeView</code> that will allow someone up the chain to apply an <code>NSDiffableDataSourceSnapshot</code> to this view. We're doing this because the view shouldn't really manage any data itself, it should just be handed content to display. Then, we'll add a function to <code>HomeViewController</code> that will generate the stubbed data and give it to the view. In a real app the data would be fetched by a networking or data base layer and handed to the view controller, but this will work well enough for our purposes.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// In HomeView</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">apply</span><span class="hljs-params">(<span class="hljs-number">_</span> snapshot: NSDiffableDataSourceSnapshot&lt;Section, Content&gt;)</span></span> {
    dataSouce.apply(snapshot)
}

<span class="hljs-comment">// In HomeViewController</span>

<span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">viewDidLoad</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">super</span>.viewDidLoad()
    updateList()
}

<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">updateList</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">var</span> snapShot = <span class="hljs-type">NSDiffableDataSourceSnapshot</span>&lt;<span class="hljs-type">Section</span>, <span class="hljs-type">Content</span>&gt;()
    snapShot.appendSections(<span class="hljs-type">Section</span>.allCases)
    <span class="hljs-type">Section</span>.allCases.forEach {
        snapShot.appendItems($<span class="hljs-number">0</span>.stubData(), toSection: $<span class="hljs-number">0</span>)
    }
    contentView.apply(snapShot)
}
</code></pre>
<p>And with that, we should be finally be able to see the first section on screen, fully populated with all the stubbed data!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1616980875146/4AJ7B8Qwm.png" alt="Screen Shot 2021-03-02 at 8.53.42 AM.png" /></p>
<h2 id="wrap-up">Wrap Up</h2>
<p>In this article we set up the view for our first cell and wrote a wrapper around it, we defined the layout for our first section, we built out the structure for our collection view and data source, and hooked it all up. In the next article we'll fill out the collection view with the rest of the content.</p>
<hr />
<p>Check out the code up to this point on <a target="_blank" href="https://github.com/dillon-mce/Airbnb-Home/tree/part-2">this branch in the repo</a>.</p>
<div class="hn-embed-widget" id="buyacoffee"></div>]]></content:encoded></item><item><title><![CDATA[Building Airbnb's UI From Scratch - Part 1]]></title><description><![CDATA[In this series of posts I am going to be building out the UI for Airbnb's "Explore" tab from scratch. This is an exercise in figuring out how to achieve the different layouts and effects you find in popular apps and I chose Airbnb because I thought i...]]></description><link>https://dilloncodes.com/airbnb-part-1</link><guid isPermaLink="true">https://dilloncodes.com/airbnb-part-1</guid><category><![CDATA[iOS]]></category><category><![CDATA[Xcode]]></category><category><![CDATA[UI]]></category><category><![CDATA[Swift]]></category><dc:creator><![CDATA[Dillon McElhinney]]></dc:creator><pubDate>Tue, 04 May 2021 14:37:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1618036907972/rA1ndsVIs.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this series of posts I am going to be building out the UI for Airbnb's "Explore" tab from scratch. This is an exercise in figuring out how to achieve the different layouts and effects you find in popular apps and I chose <a target="_blank" href="https://apps.apple.com/us/app/airbnb/id401626263?uo=4">Airbnb</a> because I thought it had several great examples. It is not intended to be used for anything other than for educational purposes and all my code will be available at <a target="_blank" href="https://github.com/dillon-mce/Airbnb-Home">this repo</a> if you want to follow along and build it yourself. A few disclaimers: </p>
<ul>
<li>For all the illustrations in the app I just took screenshots of the actual app and cropped them to size. They are not the full versions of the illustrations and they are not really even formatted correctly for use in an iOS app. I tried to spend as little time as possible on that prep. </li>
<li>We will get the fonts, colors and icons as close as we can with just using the system versions. I am pretty sure the font and icons that Airbnb actually uses would require a license and I don't care that much about making this exact. With the way we organize it, it would be pretty easy to swap in the real ones if you want to take that step yourself.</li>
<li>We will not do any business logic. We will hard code all the data and not handle any user actions. Again, this is not meant to be production code, just an exploration of some UI techniques.</li>
<li>There is a good chance that Airbnb will look different by the time you see this. Honestly, there's a non-zero chance that it will change before I finish writing this, so the live app will probably look different than what you see me build, but the layout/principles should be pretty much the same. <em>(Editorial note: it has already changed before I was able to finish writing this series, but I have a few screenshots of what the app looked like before, and we'll just build up to the spec that I created.)</em></li>
</ul>
<p>Here's what our final product will look like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1618017103718/39dgJOE5X.gif" alt="Design Spec.gif" /></p>
<p>With all that said, let's go.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/Q9jBmmE-ERo">https://youtu.be/Q9jBmmE-ERo</a></div>
<h1 id="laying-the-ground-work">Laying The Ground Work</h1>
<p>Before we actually start building stuff, we need to do some set up to make our lives easier down the line. I also promised that we'd build from scratch, so this is me showing my work. If you don't care about these set up steps and just want to get into it, jump ahead to <a target="_blank" href="https://dilloncodes.com/airbnb-part-2">First Cell and Section</a> and grab <a target="_blank" href="https://github.com/dillon-mce/Airbnb-Home/tree/part-1">this branch</a> from the repo.</p>
<h2 id="make-a-new-project">Make a new project</h2>
<p>First, we need to make a project. It'll be an iOS App that I am going to call "Airbnb Home". It'll be a Storyboard, UIKit and Swift app.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1615690287383/2TgqhU5RF.png" alt="Screen Shot 2021-02-27 at 1.43.41 PM.png" /></p>
<h2 id="set-up-the-storyboard">Set Up The Storyboard</h2>
<p>The first thing we'll do in the app is reconfigure the storyboard to have a tab controller with four empty view controllers and the one we'll be setting up. This should be the only time we need to mess with the storyboard.</p>
<p>Click on the view controller in the storyboard, click the "Embed In" button on the bottom right and click "Tab Bar Controller". I like to move the view controller down below the tab bar controller, because that will mimic the theoretical layout in the app.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1615690376519/cPpIkTnqf.png" alt="Screen Shot 2021-02-27 at 1.46.25 PM.png" /></p>
<p>Add another view controller, place it next to the first, control+click and drag from the tab bar view controller to the new view controller and click on "view controllers" under "Relationship Segue". This will embed the view controller in the tab bar.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1615690460345/5d0J4wOjZ.png" alt="Screen Shot 2021-02-27 at 1.49.23 PM.png" /></p>
<p>Repeat three more times so there is a total of five view controllers. Now you should have a layout that looks like this: </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1615690479157/4iIjOjcOB.png" alt="Screen Shot 2021-02-27 at 1.52.21 PM.png" /></p>
<p>Set the image tint to "System Pink". This is what we'll use to get close enough to Airbnb's brand color throughout the app.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1615690595722/t0x-c59y6.png" alt="Screen Shot 2021-02-27 at 1.56.26 PM.png" /></p>
<p>Click on the first tab item and set its title to "Explore" and for the image select the "magnifyingglass" system image. For all of these I just tried to pick the closest SF Symbol that I could without spending a ton of time on it. If you want to pick your own icons you're welcome to. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1615690620975/9awFdgtlF.png" alt="Screen Shot 2021-02-27 at 2.00.13 PM.png" /></p>
<p>For the rest I did:</p>
<ul>
<li>"Saved" and "heart"</li>
<li>"Trips" and "scribble.variable"</li>
<li>"Inbox" and "message"</li>
<li>"Profile" and "person.circle"</li>
</ul>
<h2 id="set-up-the-resources">Set Up The Resources</h2>
<p>Delete the existing asset catalog and drag in the one that contains all the assets we'll be using. You can get just the asset catalog this from <a target="_blank" href="https://github.com/dillon-mce/Airbnb-Home/tree/part-1/Airbnb%20Home/Resources">this repo</a>. To get these I literally took screenshots these from the Airbnb app and cropped them down to the right size.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1615690902842/PDA11CX8J.png" alt="Screen Shot 2021-02-27 at 2.10.03 PM.png" /></p>
<p>Move all the default stuff that we don't care about into a folder called 'Resources'. Right now, this is all of the files except "ViewController.swift"</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1615690940435/O75vKNx6z.png" alt="Screen Shot 2021-02-27 at 2.12.35 PM.png" /></p>
<p>If you try building at this point, you'll probably get an error saying that "Build input file could not be found..." This is because the path to the <code>Info.plist</code> file has changed and you need to tell the build settings that. Click on the project file, click on the "Airbnb Home" target, go to the "Build Settings", scroll down to the "Packaging" section and in the middle of that section there is a "Info.plist File" entry. Change that path to <code>Airbnb Home/Resources/Info.plist</code> and it will fix the problem.</p>
<p>Rename <code>ViewController</code> to be something more meaningful like <code>HomeViewController</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1615691000566/RCwf9t5XY.png" alt="Screen Shot 2021-02-27 at 2.13.33 PM.png" /></p>
<h2 id="pull-in-helpers">Pull In Helpers</h2>
<p>Add <code>Anchorage</code> as a Swift Package Dependency. We want the one from RightPoint and the default values are fine.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1615691139468/ovdz7i-5C.png" alt="Screen Shot 2021-02-27 at 2.15.12 PM.png" /></p>
<p>Add a file called <code>ProgrammaticView.swift</code> and add the <code>ProgrammaticView</code> class which will be the base class for all our views. I laid out these patterns in <a target="_blank" href="https://dilloncodes.com/how-i-organize-layout-code-in-swift">How I Layout UI Code in Swift</a>, so I won't go into too much detail on that here, but it looks like this:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// ProgrammaticView.swift</span>
<span class="hljs-keyword">import</span> UIKit

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProgrammaticView</span>: <span class="hljs-title">UIView</span> </span>{

    <span class="hljs-meta">@available</span>(*, unavailable, message: <span class="hljs-string">"Don't use init(coder:), override init(frame:) instead"</span>)
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">init</span>?(coder: <span class="hljs-type">NSCoder</span>) {
        <span class="hljs-built_in">fatalError</span>(<span class="hljs-string">"init(coder:) has not been implemented"</span>)
    }

    <span class="hljs-keyword">override</span> <span class="hljs-keyword">init</span>(frame: <span class="hljs-type">CGRect</span>) {
        <span class="hljs-keyword">super</span>.<span class="hljs-keyword">init</span>(frame: frame)

        configure()
        constrain()
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">()</span></span> {}
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">constrain</span><span class="hljs-params">()</span></span> {}
}
</code></pre>
<p>Add some helper functions on <code>UIView</code> and <code>UIStackView</code>. I like to put these in their own files, in a folder called "Extensions":</p>
<pre><code class="lang-swift"><span class="hljs-comment">// Extensions/UIView+Helpers.swift</span>

<span class="hljs-keyword">import</span> UIKit

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">UIView</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">addSubviews</span><span class="hljs-params">(<span class="hljs-number">_</span> views: UIView...)</span></span> {
        views.forEach { view <span class="hljs-keyword">in</span> addSubview(view) }
    }
}

<span class="hljs-comment">// Extensions/UIStackView+Helpers.swift</span>

<span class="hljs-keyword">import</span> UIKit

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">UIStackView</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">addArrangedSubviews</span><span class="hljs-params">(<span class="hljs-number">_</span> views: UIView...)</span></span> {
        views.forEach { view <span class="hljs-keyword">in</span> addArrangedSubview(view) }
    }
}
</code></pre>
<p>Add a helper function on <code>UIImage</code> that seems obvious to me and I'm not sure why it isn't built in, but it makes our code a lot cleaner. <code>UIImage(named:)</code> is already a failable initializer, but it takes a <code>String</code> not a <code>String?</code>, so this initializer just allows us to use an optional string without unwrapping it before passing it to the initalizer:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// Extensions/UIImage+Helpers.swift</span>

<span class="hljs-keyword">import</span> UIKit

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">UIImage</span> </span>{
    <span class="hljs-keyword">convenience</span> <span class="hljs-keyword">init</span>?(named name: <span class="hljs-type">String?</span>) {
        <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> name = name <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span> }
        <span class="hljs-keyword">self</span>.<span class="hljs-keyword">init</span>(named: name)
    }
}
</code></pre>
<p>Finally, we'll add a file for our fonts. For now, it won't do anything but the file will just be a placeholder that we come back to when we need to add custom fonts.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// Extensions/UIFont+Custom.swift</span>

<span class="hljs-keyword">import</span> UIKit

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">UIFont</span> </span>{

}
</code></pre>
<h2 id="create-views-and-model">Create Views And Model</h2>
<p>Add an empty <code>HomeView</code> and make it the view of the <code>HomeViewController</code>. I'm setting the background color to pink here just to confirm to myself that everything is working.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// HomeView.swift</span>

<span class="hljs-keyword">import</span> Anchorage
<span class="hljs-keyword">import</span> UIKit

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HomeView</span>: <span class="hljs-title">ProgrammaticView</span> </span>{
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">()</span></span> {
        backgroundColor = .systemPink
    }
}

<span class="hljs-comment">// HomeViewController.swift</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HomeViewController</span>: <span class="hljs-title">UIViewController</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-built_in">lazy</span> <span class="hljs-keyword">var</span> contentView: <span class="hljs-type">HomeView</span> = .<span class="hljs-keyword">init</span>()

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">loadView</span><span class="hljs-params">()</span></span> {
        view = contentView
    }
}
</code></pre>
<p>At this point you can run the app and it will look like this. Not quite the Airbnb app yet, but moving in the right direction.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1616978185430/aME9365uj.png" alt="Screen Shot 2021-02-27 at 2.42.13 PM.png" /></p>
<p>Add an empty <code>HeaderView</code>. We'll lay this out later in the series, but for now we will just give it a color.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// HeaderView.swift</span>

<span class="hljs-keyword">import</span> Anchorage
<span class="hljs-keyword">import</span> UIKit

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HeaderView</span>: <span class="hljs-title">ProgrammaticView</span> </span>{
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">()</span></span> {
        backgroundColor = .systemTeal
    }
}
</code></pre>
<p>Finally, we need our <code>Content</code> model. This is what everything will use to hold the content that we want to render on screen and as such, it is pretty generic. This isn't necessarily how I would organize my model in a real app, but it keeps things simple for now while we're setting up the UI. I put it in a group called "Models".</p>
<pre><code class="lang-swift"><span class="hljs-comment">// Models/Content.swift</span>

<span class="hljs-keyword">import</span> UIKit

<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Content</span>: <span class="hljs-title">Hashable</span> </span>{
    <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">Style</span> </span>{
        <span class="hljs-keyword">case</span> standard, title
    }
    <span class="hljs-keyword">let</span> title: <span class="hljs-type">String</span>
    <span class="hljs-keyword">let</span> subtitle: <span class="hljs-type">String?</span>
    <span class="hljs-keyword">let</span> image: <span class="hljs-type">String?</span>
    <span class="hljs-keyword">let</span> style: <span class="hljs-type">Style</span>

    <span class="hljs-keyword">init</span>(title: <span class="hljs-type">String</span>, subtitle: <span class="hljs-type">String?</span>, image: <span class="hljs-type">String?</span>, style: <span class="hljs-type">Style</span> = .standard) {
        <span class="hljs-keyword">self</span>.title = title
        <span class="hljs-keyword">self</span>.subtitle = subtitle
        <span class="hljs-keyword">self</span>.image = image
        <span class="hljs-keyword">self</span>.style = style
    }
}
</code></pre>
<p>This is also where we'll throw all the stubbed data, but commented out, so that you don't have to type it all up like I did.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// MARK: - Headers</span>
<span class="hljs-comment">//</span>
<span class="hljs-comment">//extension Section {</span>
<span class="hljs-comment">//    var headerContent: Content? {</span>
<span class="hljs-comment">//        switch self {</span>
<span class="hljs-comment">//        case .nearby: return nil</span>
<span class="hljs-comment">//        case .stays: return .init(title: "Live anywhere", subtitle: nil, image: nil)</span>
<span class="hljs-comment">//        case .experiences: return .init(title: "Experience the world",</span>
<span class="hljs-comment">//                                  subtitle: "Unique activities with local experts—in person or online.",</span>
<span class="hljs-comment">//                                  image: nil)</span>
<span class="hljs-comment">//        case .hosting: return .init(title: "Join millions of hosts on Airbnb", subtitle: nil, image: nil)</span>
<span class="hljs-comment">//        case .info: return .init(title: "Stay informed", subtitle: nil, image: nil)</span>
<span class="hljs-comment">//        }</span>
<span class="hljs-comment">//    }</span>
<span class="hljs-comment">//}</span>

<span class="hljs-comment">// MARK: - Stub Data</span>
<span class="hljs-comment">//</span>
<span class="hljs-comment">//extension Section {</span>
<span class="hljs-comment">//    func stubData() -&gt; [Content] {</span>
<span class="hljs-comment">//        switch self {</span>
<span class="hljs-comment">//        case .nearby:</span>
<span class="hljs-comment">//            return [</span>
<span class="hljs-comment">//                .init(title: "Estes Park", subtitle: "1.5 hour drive", image: "estes-park"),</span>
<span class="hljs-comment">//                .init(title: "Breckenridge", subtitle: "2.5 hour drive", image: "breckenridge"),</span>
<span class="hljs-comment">//                .init(title: "Grand Lake", subtitle: "3 hour drive", image: "grand-lake"),</span>
<span class="hljs-comment">//                .init(title: "Idaho Springs", subtitle: "2 hour drive", image: "idaho-springs"),</span>
<span class="hljs-comment">//                .init(title: "Glenwood Springs", subtitle: "4.5 hour drive", image: "glenwood-springs"),</span>
<span class="hljs-comment">//                .init(title: "Pagosa Springs", subtitle: "7.5 hour drive", image: "pagosa-springs"),</span>
<span class="hljs-comment">//            ]</span>
<span class="hljs-comment">//        case .stays:</span>
<span class="hljs-comment">//            return [</span>
<span class="hljs-comment">//                .init(title: "Entire homes", subtitle: nil, image: "entire-homes"),</span>
<span class="hljs-comment">//                .init(title: "Cabins and cottages", subtitle: nil, image: "cabins-cottages"),</span>
<span class="hljs-comment">//                .init(title: "Unique stays", subtitle: nil, image: "unique-stays"),</span>
<span class="hljs-comment">//                .init(title: "Pets welcome", subtitle: nil, image: "pets-welcome"),</span>
<span class="hljs-comment">//            ]</span>
<span class="hljs-comment">//        case .experiences:</span>
<span class="hljs-comment">//            return [</span>
<span class="hljs-comment">//                .init(title: "Online Experiences",</span>
<span class="hljs-comment">//                      subtitle: "Travel the world without leaving home.",</span>
<span class="hljs-comment">//                      image: "online-experiences"),</span>
<span class="hljs-comment">//                .init(title: "Experiences",</span>
<span class="hljs-comment">//                      subtitle: "Things to do wherever you are.",</span>
<span class="hljs-comment">//                      image: "experiences"),</span>
<span class="hljs-comment">//                .init(title: "Adventures",</span>
<span class="hljs-comment">//                      subtitle: "Multi-day trips with meals and stays.",</span>
<span class="hljs-comment">//                      image: "adventures"),</span>
<span class="hljs-comment">//            ]</span>
<span class="hljs-comment">//        case .hosting:</span>
<span class="hljs-comment">//            return [</span>
<span class="hljs-comment">//                .init(title: "Host your home", subtitle: nil, image: "host-your-home"),</span>
<span class="hljs-comment">//                .init(title: "Host an Online Experience", subtitle: nil, image: "host-online-experience"),</span>
<span class="hljs-comment">//                .init(title: "Host an Experience", subtitle: nil, image: "host-experience"),</span>
<span class="hljs-comment">//            ]</span>
<span class="hljs-comment">//        case .info:</span>
<span class="hljs-comment">//            return [</span>
<span class="hljs-comment">//                .init(title: "For guests", subtitle: nil, image: nil, style: .title),</span>
<span class="hljs-comment">//                .init(title: "Our COVID-19 response", subtitle: "Health and saftey updates", image: nil),</span>
<span class="hljs-comment">//                .init(title: "Cancellation options", subtitle: "Learn what's covered", image: nil),</span>
<span class="hljs-comment">//                .init(title: "Help Center", subtitle: "Get support", image: nil),</span>
<span class="hljs-comment">//</span>
<span class="hljs-comment">//                .init(title: "For hosts", subtitle: nil, image: nil, style: .title),</span>
<span class="hljs-comment">//                .init(title: "Message from Brian Chesky", subtitle: "Hear from our CEO", image: nil),</span>
<span class="hljs-comment">//                .init(title: "Resources for hosting", subtitle: "What's impacted by COVID-19", image: nil),</span>
<span class="hljs-comment">//                .init(title: "Providing frontline stays", subtitle: "Learn how to help", image: nil),</span>
<span class="hljs-comment">//</span>
<span class="hljs-comment">//                .init(title: "For COVID-19 responders", subtitle: nil, image: nil, style: .title),</span>
<span class="hljs-comment">//                .init(title: "Frontline stays", subtitle: "Learn about our program", image: nil),</span>
<span class="hljs-comment">//                .init(title: "Sign up", subtitle: "Check for housing options", image: nil),</span>
<span class="hljs-comment">//                .init(title: "Make a donation", subtitle: "Support nonprofit organizations", image: nil),</span>
<span class="hljs-comment">//</span>
<span class="hljs-comment">//                .init(title: "More", subtitle: nil, image: nil, style: .title),</span>
<span class="hljs-comment">//                .init(title: "Airbnb Newsroom", subtitle: "Latest announcements", image: nil),</span>
<span class="hljs-comment">//                .init(title: "World Health Organization", subtitle: "Education and updates", image: nil),</span>
<span class="hljs-comment">//                .init(title: "Project Lighthouse", subtitle: "Finding and fighting discrimination", image: nil),</span>
<span class="hljs-comment">//            ]</span>
<span class="hljs-comment">//        }</span>
<span class="hljs-comment">//    }</span>
<span class="hljs-comment">//}</span>
</code></pre>
<h2 id="wrap-up">Wrap Up</h2>
<p>With that, we've got all the boring stuff out of the way and we're ready to actually get started building something. See you in the next part!</p>
<hr />
<p>Check out the code up to this point on <a target="_blank" href="https://github.com/dillon-mce/Airbnb-Home/tree/part-1">this branch in the repo</a>.</p>
<div class="hn-embed-widget" id="buyacoffee"></div>]]></content:encoded></item><item><title><![CDATA[iOS View Communication]]></title><description><![CDATA[https://youtu.be/HRAO9qitgq8
I have gotten a few questions about my recent article on How I Organize Layout Code in Swift, so I thought I would follow up on that and address a couple of the common ones. In this article we're going to look at some exa...]]></description><link>https://dilloncodes.com/ios-view-communication</link><guid isPermaLink="true">https://dilloncodes.com/ios-view-communication</guid><category><![CDATA[Swift]]></category><category><![CDATA[iOS]]></category><category><![CDATA[ios app development]]></category><category><![CDATA[Xcode]]></category><dc:creator><![CDATA[Dillon McElhinney]]></dc:creator><pubDate>Mon, 25 Jan 2021 01:38:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1611539274918/2ChOT0lgJ.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/HRAO9qitgq8">https://youtu.be/HRAO9qitgq8</a></div>
<p>I have gotten a few questions about my recent article on <a target="_blank" href="https://dilloncodes.com/how-i-organize-layout-code-in-swift">How I Organize Layout Code in Swift</a>, so I thought I would follow up on that and address a couple of the common ones. In this article we're going to look at some examples of how the view communicates with the view controller, how the view controller communicates back to the view, and some ways you can segue or transition to the next screen in your flow if you want to organize your layout code like I do.</p>
<h2 id="view-greater-viewcontroller-communication">View -&gt; ViewController Communication</h2>
<p>In my last article I sort of brushed over this, saying that I would use delegation. I realize now that specific examples are helpful for people who are learning, so let's look at what that could look like in our login view from last time. I'm going to use the last configuration we looked at, with <code>ComposedLayoutViewController</code> because it is the most complex and the most similar to what I actually implement in production.</p>
<p>First, we need to define a delegate protocol for the <code>LoginStackView</code>. This is where we'll put any messages that we want to communicate from this view up to whatever needs to hear it. We constrain it to <code>AnyObject</code> so that we know it is a reference type and <code>LoginStackView</code> can hold a <code>weak</code> reference to it.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// in LoginStackView.swift</span>
<span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">LoginStackViewDelegate</span>: <span class="hljs-title">AnyObject</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didTapSubmit</span><span class="hljs-params">()</span></span>
}
</code></pre>
<p>Then we add a delegate property.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LoginStackView</span>: <span class="hljs-title">ProgrammaticView</span> </span>{
    <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> delegate: <span class="hljs-type">LoginStackViewDelegate?</span>

    <span class="hljs-comment">// other stuff...</span>
}
</code></pre>
<p>Then we add a method for the button to call, and call it when the user lifts their finger off the button, within its bounds.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LoginStackView</span>: <span class="hljs-title">ProgrammaticView</span> </span>{
    <span class="hljs-comment">// other stuff...</span>

    <span class="hljs-comment">// in configure()</span>
    submitButton.addTarget(<span class="hljs-keyword">self</span>, action: #selector(submitTapped), <span class="hljs-keyword">for</span>: .touchUpInside)

    <span class="hljs-meta">@objc</span> <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">submitTapped</span><span class="hljs-params">()</span></span> {
        delegate?.didTapSubmit()
    }
}
</code></pre>
<p>That's all we need in the <code>LoginStackView</code>. You can see how dead simple the logic is, and that's what I'm shooting for. So now we move up a level and do the same thing in <code>ComposedLayoutView</code>.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// in ComposedLayoutView.swift</span>
<span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">ComposedLayoutViewDelegate</span>: <span class="hljs-title">AnyObject</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didTapSubmit</span><span class="hljs-params">()</span></span>
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ComposedLayoutView</span>: <span class="hljs-title">ProgrammaticView</span> </span>{
    <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> delegate: <span class="hljs-type">ComposedLayoutViewDelegate?</span>

    <span class="hljs-comment">// in configure()</span>
    loginStack.delegate = <span class="hljs-keyword">self</span>
}

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">ComposedLayoutView</span>: <span class="hljs-title">LoginStackViewDelegate</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didTapSubmit</span><span class="hljs-params">()</span></span> {
        delegate?.didTapSubmit()
    }
}
</code></pre>
<p>This is basically just passing the message along from the login stack to the composed layout view's delegate. Note that it is really important that you set <code>loginStack</code>'s delegate somewhere, otherwise it will be sending messages that no one is receiving.</p>
<p>Finally, in our view controller, we just need to conform to <code>ComposedLayoutViewDelegate</code> and set the delegate on <code>contentView</code>:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ComposedLayoutViewController</span>: <span class="hljs-title">UIViewController</span> </span>{
    <span class="hljs-comment">// in loadView</span>
    contentView.delegate = <span class="hljs-keyword">self</span>
}

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">ComposedLayoutViewController</span>: <span class="hljs-title">ComposedLayoutViewDelegate</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didTapSubmit</span><span class="hljs-params">()</span></span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"The user hit submit!"</span>)
        <span class="hljs-comment">// handle submit</span>
    }
}
</code></pre>
<p>We're not actually doing anything when the user hits submit yet, but if you run the code you can see that our print statement is showing up in the console. We'll look at how to move to the next screen in a later section, but let's think about what we've got here before we move on. In the view controller, all we know is that the user hit the submit button. We don't have any access to the strings they typed in in the text fields. For logging in, those are pretty important, so we need to get that info up to the view controller. We'll do that by adding them as parameters to our delegate method.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// in LoginStackView.swift</span>
<span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">LoginStackViewDelegate</span>: <span class="hljs-title">AnyObject</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didTapSubmit</span><span class="hljs-params">(username: String?, password: String?)</span></span>
}

<span class="hljs-comment">// in LoginStackView.submitTapped()</span>
delegate?.didTapSubmit(username: usernameField.text, password: passwordField.text)

<span class="hljs-comment">// in ComposedLayoutView.swift</span>
<span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">ComposedLayoutViewDelegate</span>: <span class="hljs-title">AnyObject</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didTapSubmit</span><span class="hljs-params">(username: String?, password: String?)</span></span>
}

<span class="hljs-comment">// in ComposedLayoutView extension</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didTapSubmit</span><span class="hljs-params">(username: String?, password: String?)</span></span> {
    delegate?.didTapSubmit(username: username, password: password)
}

<span class="hljs-comment">// in ComposedLayoutViewController extension</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didTapSubmit</span><span class="hljs-params">(username: String?, password: String?)</span></span> {
    <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> username = username,
          !username.isEmpty,
          <span class="hljs-keyword">let</span> password = password,
          !password.isEmpty <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">print</span>(<span class="hljs-string">"Don't have all the required info!"</span>)
    }
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"The user hit submit with \(username) and \(password)!"</span>)
    <span class="hljs-comment">// handle submit</span>
}
</code></pre>
<p>Again, the logic to get the necessary information up to the view controller is very simple. That's what we want. Then all the logic for handling what to do will live in the view controller. Here we're just checking that we actually have all the information we need to make a decision. Let's see if we can clean that up a bit.</p>
<h2 id="cleaning-up">Cleaning Up</h2>
<p>What I don't like about this is that we have to account for optionals <em>and</em> empty strings. And, it doesn't scale super well. We are only passing up two properties here, but what if we needed to pass up five? Or ten? Our delegate methods would get unwieldy. So we're going to wrap this data up in a struct.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">LoginInfo</span> </span>{
    <span class="hljs-keyword">let</span> username: <span class="hljs-type">String</span>
    <span class="hljs-keyword">let</span> password: <span class="hljs-type">String</span>

    <span class="hljs-keyword">init</span>?(username: <span class="hljs-type">String?</span>, password: <span class="hljs-type">String?</span>) {
        <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> username = username,
              !username.isEmpty,
              <span class="hljs-keyword">let</span> password = password,
              !password.isEmpty <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span> }
        <span class="hljs-keyword">self</span>.username = username
        <span class="hljs-keyword">self</span>.password = password
    }
}
</code></pre>
<p>Here, we check for all of our required data in a failable initializer. This means that further down the line we won't have to make all the checks, either we get a <code>LoginData</code> and we have everything we need, or we get <code>nil</code> and we know we don't have everything we need. </p>
<p>To use it, we just need to update the delegate methods.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// in LoginStackView.swift</span>
<span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">LoginStackViewDelegate</span>: <span class="hljs-title">AnyObject</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didTapSubmit</span><span class="hljs-params">(loginInfo: LoginInfo?)</span></span>
}

<span class="hljs-comment">// in LoginStackView.submitTapped()</span>
<span class="hljs-keyword">let</span> loginInfo = <span class="hljs-type">LoginInfo</span>(username: usernameField.text, password: passwordField.text)
delegate?.didTapSubmit(loginInfo: loginInfo)

<span class="hljs-comment">// in ComposedLayoutView.swift</span>
<span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">ComposedLayoutViewDelegate</span>: <span class="hljs-title">AnyObject</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didTapSubmit</span><span class="hljs-params">(loginInfo: LoginInfo?)</span></span>
}

<span class="hljs-comment">// in ComposedLayoutView extension</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didTapSubmit</span><span class="hljs-params">(loginInfo: LoginInfo?)</span></span> {
    delegate?.didTapSubmit(loginInfo: loginInfo)
}

<span class="hljs-comment">// in ComposedLayoutViewController extension</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didTapSubmit</span><span class="hljs-params">(loginInfo: LoginInfo?)</span></span> {
    <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> loginInfo = loginInfo <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-built_in">print</span>(<span class="hljs-string">"Don't have all the required info!"</span>)
        }
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"The user hit submit with \(username) and \(password)!"</span>)
    <span class="hljs-comment">// handle submit</span>
}
</code></pre>
<p>I like how clean that makes our logic, and it also nicely wraps up the information we'll need to pass to whatever form of authentication we'll be using in our app. But that leads us to the next question. What should we do if the user taps the submit button without the required info? Right now we are just printing a statement and doing nothing else. It is probably better UX to let the user know what the problem is. So let's see how we might handle that.</p>
<h2 id="view-controller-greater-view-communication">View Controller -&gt; View Communication</h2>
<p>First, I add a warning label to <code>LoginStackView</code></p>
<pre><code class="lang-swift"><span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> warningLabel = <span class="hljs-type">UILabel</span>()

<span class="hljs-comment">// in configure()</span>
warningLabel.text = <span class="hljs-string">"You must supply a username and password to log in."</span>
warningLabel.numberOfLines = <span class="hljs-number">0</span>
warningLabel.textAlignment = .center
warningLabel.textColor = .systemRed
warningLabel.font = .preferredFont(forTextStyle: .callout)
<span class="hljs-comment">// warningLabel.isHidden = true</span>

<span class="hljs-comment">// in constrain()</span>
stackView.addArrangedSubviews(usernameField, passwordField, warningLabel, submitButton)

warningLabel.horizontalAnchors == horizontalAnchors
</code></pre>
<p>We want it to be hidden by default, but I usually comment that line out until I've got it looking the way I want. Once I do, I uncomment <code>warningLabel.isHidden = true</code> and move on to adding a method to show it.</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">showWarning</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">if</span> warningLabel.isHidden {
        <span class="hljs-type">UIView</span>.animate(withDuration: <span class="hljs-number">0.3</span>) {
            <span class="hljs-keyword">self</span>.warningLabel.isHidden = <span class="hljs-literal">false</span>
        }
    }
}
</code></pre>
<p>First, I check to make sure that it is hidden and if it is, then I animate it to be shown. If it is already shown, we don't need to do anything.</p>
<p>So now we have our label and a function to call to show it. How do we get access to that in our view controller? There are a couple of ways and I've seen both used well, it just depends on what works best for your use case. The first is you could define a method on <code>ComposedLayoutView</code> (the middle man) that calls <code>showWarning</code> on its child <code>LoginStackView</code> and then call <em>that</em> method from <code>ComposedLayoutViewController</code>. That works, but it can get kind of messy if there are a lot of views involved. So here we're going to follow another method, one that Apple's frameworks use a ton. We're going to pass up a reference to the <code>LoginStackView</code> in the delegate method calls. Then, the view controller can just call <code>showWarning</code> on that. It is also a convenient way for the view controller to know <em>which</em> login stack called <code>didTapSubmit</code>, if there happen to be multiple. So first, we need to update our delegate methods again.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// in LoginStackView.swift</span>
<span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">LoginStackViewDelegate</span>: <span class="hljs-title">AnyObject</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didTapSubmit</span><span class="hljs-params">(<span class="hljs-number">_</span> loginStack: LoginStackView, loginInfo: LoginInfo?)</span></span>
}

<span class="hljs-comment">// in LoginStackView.submitTapped()</span>
<span class="hljs-keyword">let</span> loginInfo = <span class="hljs-type">LoginInfo</span>(username: usernameField.text, password: passwordField.text)
delegate?.didTapSubmit(<span class="hljs-keyword">self</span>, loginInfo: loginInfo)

<span class="hljs-comment">// in ComposedLayoutView.swift</span>
<span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">ComposedLayoutViewDelegate</span>: <span class="hljs-title">AnyObject</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didTapSubmit</span><span class="hljs-params">(<span class="hljs-number">_</span> loginStack: LoginStackView, loginInfo: LoginInfo?)</span></span>
}

<span class="hljs-comment">// in ComposedLayoutView extension</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didTapSubmit</span><span class="hljs-params">(<span class="hljs-number">_</span> loginStack: LoginStackView, loginInfo: LoginInfo?)</span></span> {
    delegate?.didTapSubmit(loginStack, loginInfo: loginInfo)
}

<span class="hljs-comment">// in ComposedLayoutViewController extension</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didTapSubmit</span><span class="hljs-params">(<span class="hljs-number">_</span> loginStack: LoginStackView, loginInfo: LoginInfo?)</span></span> {
    <span class="hljs-comment">// same as before...</span>
}
</code></pre>
<p>Now we have everything we need to show the warning when the user didn't supply all the required info.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// ComposedLayoutViewController.didTapSubmit()</span>
<span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> loginInfo = loginInfo <span class="hljs-keyword">else</span> {
    <span class="hljs-keyword">return</span> loginStack.showWarning()
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1611435826015/nAkwHcjzv.gif" alt="warning-label.gif" /></p>
<p>That should give you a pretty good idea of how I work through the communication back and forth between the views and view controllers. There are always different scenarios and edge cases, but I pretty much always start with asking myself is this "view logic" or "business logic"? And then figure out how to get the responsibilities into their respective places.</p>
<h2 id="getting-to-the-next-screen">Getting To The Next Screen</h2>
<p>So that is a good look at how I organize the communications that goes on <em>within</em> a screen. But how do we get to the next one? Should we use segues? That is what most people who are used to using storyboards would reach for. And you can still use a segue if that is how you want to do it, but here I will show an easy (and somewhat naive) way to do it and then I'll show a simple example of <a target="_blank" href="https://youtu.be/a1g3k3NObkE">Soroush Khalou's concept of a coordinator</a>, which he wrote about in <a target="_blank" href="https://khanlou.com/2015/10/coordinators-redux/">this blog post</a>.</p>
<p>First, I'm going to add a few simple views for us to transition to. I also added a corresponding one called <code>FailedLoginViewController</code>, which is pretty much identical but has a different message. These aren't intended to be real views right now, just enough for us to illustrate the transition concept.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LoggedInViewController</span>: <span class="hljs-title">UIViewController</span> </span>{

    <span class="hljs-built_in">lazy</span> <span class="hljs-keyword">var</span> contentView: <span class="hljs-type">LoggedInView</span> = .<span class="hljs-keyword">init</span>()

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">loadView</span><span class="hljs-params">()</span></span> {
        view = contentView
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LoggedInView</span>: <span class="hljs-title">ProgrammaticView</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> label = <span class="hljs-type">UILabel</span>()

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">()</span></span> {
        backgroundColor = .systemBackground

        label.text = <span class="hljs-string">"You're logged in!"</span>
        label.textColor = .systemGreen
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">constrain</span><span class="hljs-params">()</span></span> {
        addSubview(label)

        label.centerAnchors == centerAnchors
    }
}
</code></pre>
<p>Then I'm going to add a mock authenticator. This is going to be an object that we can give a <code>LoginInfo</code> instance to and it will attempt to authenticate the user. In a real app, this will involve some networking and more complex code, but it is likely that the interface you'll use will be pretty similiar. Right now, we don't care <em>how</em> the authentication happens, we just care what the result is.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Authenticator</span> </span>{
    <span class="hljs-keyword">static</span> <span class="hljs-keyword">let</span> shared = <span class="hljs-type">Authenticator</span>()

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">init</span>() {}

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">authenticateUser</span><span class="hljs-params">(with loginInfo: LoginInfo)</span></span> -&gt; <span class="hljs-type">Bool</span> {
        <span class="hljs-keyword">return</span> loginInfo.username.<span class="hljs-built_in">count</span> &gt;= <span class="hljs-number">4</span> &amp;&amp; loginInfo.password == <span class="hljs-string">"1234"</span>
    }
}
</code></pre>
<p>Obviously, there are a lot of problems with this <code>Authenticator</code>, but this isn't an article on authentication and it is good enough for us to use for now to test out our flows.</p>
<p>All we need to do now is use our <code>authenticateUser</code> method to determine which view to show:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// ComposedLayoutViewController.didTapSubmit()</span>
<span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> loginInfo = loginInfo <span class="hljs-keyword">else</span> {
    <span class="hljs-keyword">return</span> loginStack.showWarning()
}
<span class="hljs-keyword">let</span> isValidUser = <span class="hljs-type">Authenticator</span>.shared.authenticateUser(with: loginInfo)
<span class="hljs-keyword">if</span> isValidUser {
    show(<span class="hljs-type">LoggedInViewController</span>(), sender: <span class="hljs-keyword">self</span>)
} <span class="hljs-keyword">else</span> {
    show(<span class="hljs-type">FailedLoginViewController</span>(), sender: <span class="hljs-keyword">self</span>)
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1611436237323/PIZixnTSi.gif" alt="show-presentation.gif" /></p>
<p>This works, but there are a couple of problems with it. First, we are using <code>show</code>, which will present the view controller modally here because our <code>ComposedLayoutViewController</code> isn't inside of a <code>UINavigationController</code>. It also means that the <code>ComposedLayoutViewController</code> is still in the view hierarchy underneath the new view. When we're logging in, we probably want to replace the window's root view controller, so that we can clear out the views used for logging in. To do that, we need access to the window. Unfortunately, there's not a great way to get access to that from the view controller (mostly because window manipulation should be done at a higher level). Let's look at how we <em>could</em> do this, and then quickly move to a better way.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> sceneDelegate = <span class="hljs-type">UIApplication</span>.shared.connectedScenes.first?.delegate <span class="hljs-keyword">as</span>? <span class="hljs-type">SceneDelegate</span>,
      <span class="hljs-keyword">let</span> window = sceneDelegate.window <span class="hljs-keyword">else</span> {
    <span class="hljs-keyword">return</span> show(<span class="hljs-type">LoggedInViewController</span>(), sender: <span class="hljs-keyword">self</span>)
}
window.rootViewController = <span class="hljs-type">LoggedInViewController</span>()
<span class="hljs-type">UIView</span>.transition(with: window, duration: <span class="hljs-number">0.3</span>, options: .transitionFlipFromRight, animations: <span class="hljs-literal">nil</span>, completion: <span class="hljs-literal">nil</span>)
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1611436466255/XXr5f_Xqv.gif" alt="flip-transition.gif" /></p>
<p>Again, this technically works. If you throw a print statement in the <code>deint</code> of <code>ComposedLayoutViewController</code> you can see that we are getting rid of that altogether, so it is no longer hanging around in memory. But it very tightly couples our <code>ComposedLayoutViewController</code> to the views that come next. If we keep it like this, we are likely to end up with a bunch of complex and confusing logic in this method as we come across edge cases of other places in the app that we want to authenticate the user. Imagine if the user tapped a deeplink into the app, that should take them to some specific content, but they need to log in first, or imagine if you want to A/B test different home screens, etc. Depending on the structure of your app, there might be any number of views that should come after this login view. So let's take a look at how we can decouple this a little bit.</p>
<h2 id="coordinators">Coordinators</h2>
<p>The concept of coordinators was, as far as I know, first posited by Soroush Khanlou, at least in the iOS world. I'm not going to go super in depth on it here, because he's already already got some great material on what they are and how to use them. What I will do is show you how I typically use them and how it can clean up the code we're looking at here. First, we're going to define a <code>Coordinator</code> class. This is where the logic for the flows between individual screens will live.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Coordinator</span> </span>{
    <span class="hljs-keyword">let</span> window: <span class="hljs-type">UIWindow</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> viewController: <span class="hljs-type">UIViewController?</span>

    <span class="hljs-keyword">init</span>(window: <span class="hljs-type">UIWindow</span>) {
        <span class="hljs-keyword">self</span>.window = window
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">start</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">if</span> <span class="hljs-type">Authenticator</span>.shared.isLoggedIn {
            viewController = <span class="hljs-type">LoggedInViewController</span>()
        } <span class="hljs-keyword">else</span> {
            viewController = <span class="hljs-type">ComposedLayoutViewController</span>()
        }
        window.rootViewController = viewController
        window.makeKeyAndVisible()
    }
}
</code></pre>
<p>I usually make my top level coordinator hold a reference to the window because it make the manipulations that you do at the beginning of the app lifecycle easier to do, but you could also just pass it a reference to your root tab bar controller or navigation controller, if that makes more sense in your use case. It'll also need to hold references to the view controllers or child coordinators that it is in charge of. Finally, has a <code>start</code> function. This is where the person who makes this coordinator will tell it to kick off its flow. In this case, the start method checks if the user is logged in and make the correct view controller the root.</p>
<p>Side note, I made a slight modification to our <code>Authenticator</code> to get this interface:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// in Authenticator</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">authenticateUser</span><span class="hljs-params">(with loginInfo: LoginInfo)</span></span> -&gt; <span class="hljs-type">Bool</span> {
    isLoggedIn = loginInfo.username.<span class="hljs-built_in">count</span> &gt;= <span class="hljs-number">4</span> &amp;&amp; loginInfo.password == <span class="hljs-string">"1234"</span>
    <span class="hljs-keyword">return</span> isLoggedIn
}

<span class="hljs-keyword">private</span>(<span class="hljs-keyword">set</span>) <span class="hljs-keyword">var</span> isLoggedIn = <span class="hljs-literal">false</span>
</code></pre>
<p>Then, we actually need to use our new coordinator, which we'll do in <code>SceneDelegate</code>:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// need to keep a reference to this so it isn't deinitialized</span>
<span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> coordinator: <span class="hljs-type">Coordinator?</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">scene</span><span class="hljs-params">(<span class="hljs-number">_</span> scene: UIScene,
           willConnectTo session: UISceneSession,
           options connectionOptions: UIScene.ConnectionOptions)</span></span> {
    <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> windowScene = (scene <span class="hljs-keyword">as</span>? <span class="hljs-type">UIWindowScene</span>) <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> }
    <span class="hljs-keyword">let</span> window = <span class="hljs-type">UIWindow</span>(windowScene: windowScene)

    coordinator = <span class="hljs-type">Coordinator</span>(window: window)
    <span class="hljs-keyword">self</span>.window = window

    coordinator?.start()
}
</code></pre>
<p>Here, we just make a window for our scene, we hand it off to the coordinator, and we tell the coordinator to start. With just that little bit of code, our app will now check if the user is logged in during launch and only show them the view controller that is relevant to their current state. An added bonus is this means we can get rid of the <code>Main.storyboard</code> altogether. Then, with the exception of the launch sreen, our app is now totally initialized in code.</p>
<p>So what about the login logic? For that, we'll just define another delegate protocol and hand off the information from the view controller to the <code>Coordinator</code>.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">ComposedLayoutViewControllerDelegate</span>: <span class="hljs-title">AnyObject</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">attemptedLogin</span><span class="hljs-params">(with loginInfo: LoginInfo)</span></span>
}

<span class="hljs-comment">// in ComposedLayoutViewController</span>
<span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> delegate: <span class="hljs-type">ComposedLayoutViewControllerDelegate?</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didTapSubmit</span><span class="hljs-params">(<span class="hljs-number">_</span> loginStack: LoginStackView, loginInfo: LoginInfo?)</span></span> {
    <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> loginInfo = loginInfo <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span> loginStack.showWarning()
    }
    delegate?.attemptedLogin(with: loginInfo)
}
</code></pre>
<p>Notice how simple <code>didTapSubmit</code> is now. We check to make sure we have all the information we need to attempt a login and if we don't, we respond <em>within this view</em>. This is the jurisdiciton of this view controller, so it just tells the view what to do. If we're ready to move beyond this view (i.e. we have all the information we need), we just pass it up to the delegate.</p>
<p>Finally, the coordinator needs to adopt the delegate protocol and make itself the view controller's delegate.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// in Coordinator.start()</span>
<span class="hljs-keyword">let</span> loginVC = <span class="hljs-type">ComposedLayoutViewController</span>()
loginVC.delegate = <span class="hljs-keyword">self</span>
viewController = loginVC

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">attemptedLogin</span><span class="hljs-params">(with loginInfo: LoginInfo)</span></span> {
    <span class="hljs-keyword">let</span> isLoggedIn = <span class="hljs-type">Authenticator</span>.shared.authenticateUser(with: loginInfo)
    <span class="hljs-keyword">if</span> isLoggedIn {
        viewController = <span class="hljs-type">LoggedInViewController</span>()
        window.rootViewController = viewController
        <span class="hljs-type">UIView</span>.transition(with: window, duration: <span class="hljs-number">0.3</span>, options: .transitionFlipFromRight, animations: <span class="hljs-literal">nil</span>, completion: <span class="hljs-literal">nil</span>)
    } <span class="hljs-keyword">else</span> {
        viewController?.show(<span class="hljs-type">FailedLoginViewController</span>(), sender: viewController)
    }
}
</code></pre>
<p>Again, this is nice and clean because our coordinator already has a reference to the window, so we don't have to jump through hoops to get that. And we are totally free to do whatever we want in this function. We could present any view controller we want, based on a feature flag or an API call, we could spin up a child coordinator and have that make the decision, etc. In this case, if the login is successful I am replacing the root view controller with the logged in view, and giving it a little animation. If it isn't successful, I just pop up a message modally to let the user know something went wrong.</p>
<h2 id="wrap-up">Wrap Up</h2>
<p>We've covered a lot of ground here. We looked at how I have my views communicate with my view controllers, we've looked at how those view controllers communicate back down to their views, and we've looked at a couple ways you can organize the logic for the flows between screens. As always, I hope it has been helpful and maybe given you an opportunity to think about how you organize your own code. If you have any questions or suggestions, throw them in the comments!</p>
<hr />
<p>Check out <a target="_blank" href="https://github.com/dillon-mce/ProgrammaticUI/tree/part-2">all the code from this article on github</a></p>
<div class="hn-embed-widget" id="buyacoffee"></div>]]></content:encoded></item><item><title><![CDATA[Sorting in Swift]]></title><description><![CDATA[https://youtu.be/Jtb_f5EY2DY
Sometimes you have a collection of elements that are not in the order that you need them. Fortunately, Swift gives us an easy way to get them from that chaotic state into an ordered state. In this article we're going to l...]]></description><link>https://dilloncodes.com/sorting-in-swift</link><guid isPermaLink="true">https://dilloncodes.com/sorting-in-swift</guid><category><![CDATA[Swift]]></category><category><![CDATA[iOS]]></category><category><![CDATA[ios app development]]></category><category><![CDATA[sorting]]></category><dc:creator><![CDATA[Dillon McElhinney]]></dc:creator><pubDate>Mon, 18 Jan 2021 00:24:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1610929112693/Qb8pDl0Ms.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/Jtb_f5EY2DY">https://youtu.be/Jtb_f5EY2DY</a></div>
<p>Sometimes you have a collection of elements that are not in the order that you need them. Fortunately, Swift gives us an easy way to get them from that chaotic state into an ordered state. In this article we're going to look at how to use the sort methods, how we can clean up the code around them, and how we can sort objects that aren't necessarily sortable as a whole.</p>
<h2 id="breaking-it-down">Breaking It Down</h2>
<p>If you've taken a computer science class, or are learning fundamentals, you have probably learned about <a target="_blank" href="https://en.wikipedia.org/wiki/Insertion_sort">insertion sort</a>, <a target="_blank" href="https://en.wikipedia.org/wiki/Quicksort">quicksort</a> or <a target="_blank" href="https://en.wikipedia.org/wiki/Merge_sort">merge sort</a> or one of the others and how to implement them. That's good stuff to know, but in practice we don't usually want to think at that level because we are likely to make mistakes in our code and there is almost always a way to optimize things further beyond what we know. So Swift has these two methods <code>sort(by:)</code> and <code>sorted(by:)</code>. They provide an abstraction around what sorting method is used, so you don't have to care. They just ask you for a closure that they can call on any two elements and you have to return a boolean that lets the sort function know if those two elements are in increasing order.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">mutating</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">sort</span><span class="hljs-params">(by areInIncreasingOrder: <span class="hljs-params">(Element, Element)</span></span></span> <span class="hljs-keyword">throws</span> -&gt; <span class="hljs-type">Bool</span>) <span class="hljs-keyword">rethrows</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">sorted</span><span class="hljs-params">(by areInIncreasingOrder: <span class="hljs-params">(Element, Element)</span></span></span> <span class="hljs-keyword">throws</span> -&gt; <span class="hljs-type">Bool</span>) <span class="hljs-keyword">rethrows</span> -&gt; [<span class="hljs-type">Element</span>]
</code></pre>
<p>Let's dig into that, but first I just want to note that the main difference between them is  <code>sort</code> sorts your collection in place and <code>sorted</code> returns a new array with the elements sorted. I almost always use <code>sorted</code>, but there are times when performance can be improved significantly by using <code>sort</code> instead, so it is good to keep in the back of your head. For the rest of this article, I'm only going to refer to <code>sorted</code>.</p>
<p>Looking at the function signature might be a little intimidating. I know it was for me when I was first trying to read the documentation. I mean, what is <code>rethrows</code>? It turns out, the <code>throws</code> and <code>rethrows</code> in this context are things you can ignore until you need them. (If you can't stand not knowing, check out <a target="_blank" href="https://www.hackingwithswift.com/example-code/language/how-to-use-the-rethrows-keyword">Paul Hudson's article on the topic</a>.) So let's just pretend that this is the signature:</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">sorted</span><span class="hljs-params">(by areInIncreasingOrder: <span class="hljs-params">(Element, Element)</span></span></span> -&gt; <span class="hljs-type">Bool</span>) -&gt; [<span class="hljs-type">Element</span>]
</code></pre>
<p>This is a function which you can call on a collection, it's only parameter is a closure that takes two <code>Element</code>s and returns a <code>Bool</code>, and the function itself returns an <code>[Element]</code>. The closure is where you tell the sorter how to sort.  It might look something like this:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> unsortedArray = [<span class="hljs-number">3</span>, <span class="hljs-number">2</span>, <span class="hljs-number">5</span>, <span class="hljs-number">1</span>, <span class="hljs-number">4</span>]

<span class="hljs-keyword">let</span> goingUp = unsortedArray.sorted(by: { first, second -&gt; <span class="hljs-type">Bool</span> <span class="hljs-keyword">in</span>
    <span class="hljs-keyword">return</span> first &lt; second
})
<span class="hljs-comment">// goingUp == [1, 2, 3, 4, 5]</span>

<span class="hljs-keyword">let</span> goingDown = unsortedArray.sorted(by: { first, second -&gt; <span class="hljs-type">Bool</span> <span class="hljs-keyword">in</span>
    <span class="hljs-keyword">return</span> first &gt; second
})
<span class="hljs-comment">// goingDown == [5, 4, 3, 2, 1]</span>
</code></pre>
<p>So whatever method is actually used for sorting, it will go through and any time it needs to make a decision of "should <em>this</em> element go first or <em>that</em> one?" it will call this closure and based on the answer it will know where each element needs to go. This is already pretty powerful, but it gets even better. </p>
<h2 id="same-thing-but-shorter">Same Thing, But Shorter</h2>
<p>We can communicate the same thing in way fewer characters because of some nice syntactic sugar in Swift. Let's see all the steps we can take to reduce our code here.</p>
<p>First, we can make it a trailing closure and get rid of the argument label.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> goingUp = unsortedArray.sorted() { first, second -&gt; <span class="hljs-type">Bool</span> <span class="hljs-keyword">in</span>
    <span class="hljs-keyword">return</span> first &lt; second
}
</code></pre>
<p>We can get rid of the return type definition, because the sorted method already has that as a part of its signature so it can be inferred here. If you try to write a closure here that doesn't return a boolean, the compiler will complain.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> goingUp = unsortedArray.sorted() { first, second <span class="hljs-keyword">in</span>
    <span class="hljs-keyword">return</span> first &lt; second
}
</code></pre>
<p>Because this is one line, we can also get rid of the <code>return</code> keyword at the end of the closure. The compiler will assume that you intend to return the result of this line (as long as it evaluates to a boolean).</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> goingUp = unsortedArray.sorted() { first, second <span class="hljs-keyword">in</span>
    first &lt; second
}
</code></pre>
<p>We also get some free parameter names that are based on the argument position in the closure, so we can get rid of the parameter names too. <code>$0</code> will be the first argument, counting up from there.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> goingUp = unsortedArray.sorted() {
    $<span class="hljs-number">0</span> &lt; $<span class="hljs-number">1</span>
}
</code></pre>
<p>We can also get rid of the parentheses because there are no other arguments except this trailing closure. At the same time, this is so short we can even fit it on one line.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> goingUp = unsortedArray.sorted { $<span class="hljs-number">0</span> &lt; $<span class="hljs-number">1</span> }
</code></pre>
<p>That is much shorter, and it puts the central logic right in front of you. We're sorting in the order where the smaller elements will come first. But don't feel bad if you get mixed up with such short statements, especially when it hasn't made it into your muscle memory yet. I think it is a good practice to start with the fully expanded version while you're working the logic out and then go through these steps to try to condense it from there. Over time, it will become second nature for you.</p>
<p>In this case, we can actually take it even farther. Because operators in Swift are basically just special syntax for functions, and functions are themselves closures, and because the signature for <code>&lt;</code> matches the signature for the closure we're looking for in <code>sorted</code>, as long as the elements in our array are <code>Comparable</code> we can just pass the operator as the parameter.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> goingUp = unsortedArray.sorted(by: &lt;)
</code></pre>
<p>Last but not least, if we want to sort in ascending order (which we do in this case) and our elements are <code>Comparable</code>, we don't actually have to provide any argument because that is the default.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> goingUp = unsortedArray.sorted()
</code></pre>
<h2 id="sorting-on-properties">Sorting On Properties</h2>
<p>Another thing that makes <code>sorted</code> so powerful is that you don't have to sort on the whole element, you can sort on one of its properties instead. This is super helpful because it is not uncommon to have an object where the whole object is not <code>Comparable</code>, but some of its properties are. For example, think about how you would sort a group of people. Would you sort them by age? or by name? by height? Whatever you chose, you would almost certainly sort them by some specific attribute (or combination of attributes), not by them as whole people. It’s hard to even imagine what that would mean. Besides that, in some contexts it may make sense to sort them by name, but in others it may make more sense to sort them by interest, or height, or hair color. And the same is true in our code. Fortunately, Swift makes this really easy to do.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Person</span> </span>{
    <span class="hljs-keyword">let</span> name: <span class="hljs-type">String</span>
    <span class="hljs-keyword">let</span> createdAt: <span class="hljs-type">Date</span> = <span class="hljs-type">Date</span>()
}
<span class="hljs-keyword">let</span> wayne = <span class="hljs-type">Person</span>(name: <span class="hljs-string">"Wayne"</span>)
<span class="hljs-keyword">let</span> derry = <span class="hljs-type">Person</span>(name: <span class="hljs-string">"Derry"</span>)
<span class="hljs-keyword">let</span> dan = <span class="hljs-type">Person</span>(name: <span class="hljs-string">"Dan"</span>)

<span class="hljs-keyword">let</span> unsortedPeople = [dan, wayne, derry]

<span class="hljs-keyword">let</span> alphabetical = unsortedPeople.sorted { $<span class="hljs-number">0</span>.name &lt; $<span class="hljs-number">1</span>.name }
<span class="hljs-comment">// alphabetical == [dan, derry, wayne]</span>

<span class="hljs-keyword">let</span> oldestFirst = unsortedPeople.sorted { $<span class="hljs-number">0</span>.createdAt &lt; $<span class="hljs-number">1</span>.createdAt }
<span class="hljs-comment">// oldestFirst == [wayne, derry, dan]</span>
</code></pre>
<p>In the first example, we sort on the person's name and the second we sort on the date when that person was created.  If your object has further nested properties, you could sort on those too! It just comes down to whatever makes the most sense in your use case. And that is the beauty of <code>sorted</code>. You don't have to worry about <em>how</em> to sort elements, you only have to think about <em>what</em> you want to sort them by.</p>
<p>For some extra sugar, you can even add <a target="_blank" href="https://www.swiftbysundell.com/articles/the-power-of-key-paths-in-swift/">a simple extension that I picked up from John Sundell</a> that will let you sort on a keypath. I won't go into detail here about what keypaths are or how to use them, but check out John's article if you're interested. Here's what that looks like.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">Sequence</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">sorted</span>&lt;T: Comparable&gt;<span class="hljs-params">(by keyPath: KeyPath&lt;Element, T&gt;)</span></span> -&gt; [<span class="hljs-type">Element</span>] {
        sorted { $<span class="hljs-number">0</span>[keyPath: keyPath] &lt; $<span class="hljs-number">1</span>[keyPath: keyPath] }
    }
}

<span class="hljs-keyword">let</span> alphabetical = unsortedPeople.sorted(by: \.name)
<span class="hljs-comment">// alphabetical == [dan, derry, wayne]</span>
</code></pre>
<p>A couple of things to note about this extension. First, this method assumes that you want to sort in ascending order, but you can customize it however makes sense for your use case. You could even further extend it to also take in a closure so that the sort order could be defined at the call site. But I'll leave that as an exercise for you to try out if you want.</p>
<p>Second, notice that the extension is on <code>Sequence</code>, not on <code>Array</code>. This is because of another very powerful feature of Swift, the collection protocols. Again, I'm not going to go into a ton of detail here, but check out <a target="_blank" href="https://harshil.net/blog/swift-sequence-collection-array">Harshil Shah's article on Swift's Collection Types</a>. It is about as close to comprehensive as I have come across, and he does a great job of making it understandable. What it means for us here is that this extension will add this <code>sorted</code> method to any type that conforms to <code>Sequence</code>. That means not only can we call it on an <code>Array</code>, we can also call it on a <code>Set</code>, a <code>Dictionary</code>, or even on a <code>String</code>(although I'm not sure how often that is useful).</p>
<pre><code class="lang-swift"><span class="hljs-keyword">let</span> <span class="hljs-keyword">set</span> = <span class="hljs-type">Set</span>([wayne, derry, dan])
<span class="hljs-keyword">let</span> alphabetical = <span class="hljs-keyword">set</span>.sorted(by: \.name))
<span class="hljs-comment">// alphabetical == [dan, derry, wayne]</span>

<span class="hljs-keyword">let</span> numberOfPuppers = [wayne: <span class="hljs-number">6</span>, derry: <span class="hljs-number">4</span>, dan: <span class="hljs-number">9</span>]
<span class="hljs-keyword">let</span> byLeastDrunk = numberOfPuppers.sorted(by: \.value)
<span class="hljs-comment">// byLeastDrunk == [(key: Derry, value: 4), (key: Wayne, value: 6), (key: Dan, value: 9)]</span>

<span class="hljs-keyword">let</span> sortedLetters = <span class="hljs-string">"Sushis and sashimis."</span>.sorted(by: \.description)
<span class="hljs-comment">// sortedLetters == [" ", " ", ".", "S", "a", "a", "d", "h", "h", "i", "i", "i", "m", "n", "s", "s", "s", "s", "s", "u"]</span>
</code></pre>
<h2 id="wrap-up">Wrap Up</h2>
<p>So there you have it. We've looked at how to use <code>sort/sorted</code> even though their signatures might be a little intimidating. We've looked at how to use them with as few characters of code as possible. And we've looked at how to sort objects that may not make sense to sort as a whole, but have properties you can sort on. I hope that gives you a better picture of how to use these methods. If you have any questions, tips, tricks or stories about sorting in Swift, be sure to share them in the comments.</p>
<div class="hn-embed-widget" id="buyacoffee"></div>]]></content:encoded></item><item><title><![CDATA[How I Organize Layout Code In Swift]]></title><description><![CDATA[https://youtu.be/gZSayiwa5Ao
I was asked the other day how I organize my layout code when I am building my UI programmatically using UIKit. In this post I will walk through a variety of things that I have tried over time and end with what my typical ...]]></description><link>https://dilloncodes.com/how-i-organize-layout-code-in-swift</link><guid isPermaLink="true">https://dilloncodes.com/how-i-organize-layout-code-in-swift</guid><category><![CDATA[iOS]]></category><category><![CDATA[UI]]></category><category><![CDATA[Xcode]]></category><category><![CDATA[Swift]]></category><dc:creator><![CDATA[Dillon McElhinney]]></dc:creator><pubDate>Mon, 11 Jan 2021 04:59:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1610334891450/8F3RfDtYS.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/gZSayiwa5Ao">https://youtu.be/gZSayiwa5Ao</a></div>
<p>I was asked the other day how I organize my layout code when I am building my UI programmatically using <code>UIKit</code>. In this post I will walk through a variety of things that I have tried over time and end with what my typical organization now looks like. It will not teach you <a target="_blank" href="https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/index.html">how auto layout works</a>. It will also not make an argument for why you should lay out your UI this way. Different methods work for different people. If you love storyboards, run with them. All I intend to share is what I have landed on for now. (And hopefully when I look back on this article in a year, it will have further evolved, because I kept learning and growing.)</p>
<p>This is the layout we'll look at in these examples. All of the different pieces of code you find here should lead to this same layout.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1610233201632/Fi0xaCyyo.png" alt="Basic Login Layout" /></p>
<h2 id="basic-layout">Basic Layout</h2>
<p>The first method I learned, and honestly what you'll see in a lot of tutorials, is to lay things out in <code>viewDidLoad</code> in your <code>ViewController</code> using a series of methods on <code>NSLayoutAnchor</code> called <code>constraint</code>. This results in code that looks like this:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BasicLayoutViewController</span>: <span class="hljs-title">UIViewController</span> </span>{

    <span class="hljs-comment">// define the elements</span>
    <span class="hljs-keyword">let</span> usernameField = <span class="hljs-type">UITextField</span>()
    <span class="hljs-keyword">let</span> passwordField = <span class="hljs-type">UITextField</span>()
    <span class="hljs-keyword">let</span> submitButton = <span class="hljs-type">UIButton</span>(type: .custom)

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">viewDidLoad</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">super</span>.viewDidLoad()

         <span class="hljs-comment">// configure them</span>
        view.backgroundColor = .secondarySystemBackground

        usernameField.placeholder = <span class="hljs-string">"Username"</span>
        usernameField.borderStyle = .roundedRect

        passwordField.placeholder = <span class="hljs-string">"Password"</span>
        passwordField.textContentType = .password
        passwordField.isSecureTextEntry = <span class="hljs-literal">true</span>
        passwordField.borderStyle = .roundedRect

        submitButton.setTitle(<span class="hljs-string">"Log in"</span>, <span class="hljs-keyword">for</span>: .normal)
        submitButton.backgroundColor = .systemBlue
        submitButton.layer.cornerRadius = <span class="hljs-number">8</span>

        <span class="hljs-comment">// add them to the view hierarchy and constrain them</span>
        view.addSubview(usernameField)
        view.addSubview(passwordField)
        view.addSubview(submitButton)

        usernameField.translatesAutoresizingMaskIntoConstraints = <span class="hljs-literal">false</span>
        usernameField.leadingAnchor.constraint(equalTo: passwordField.leadingAnchor).isActive = <span class="hljs-literal">true</span>
        usernameField.trailingAnchor.constraint(equalTo: passwordField.trailingAnchor).isActive = <span class="hljs-literal">true</span>

        passwordField.translatesAutoresizingMaskIntoConstraints = <span class="hljs-literal">false</span>
        passwordField.topAnchor.constraint(equalTo: usernameField.bottomAnchor, constant: <span class="hljs-number">8</span>).isActive = <span class="hljs-literal">true</span>
        passwordField.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = <span class="hljs-literal">true</span>
        passwordField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: <span class="hljs-number">20</span>).isActive = <span class="hljs-literal">true</span>
        passwordField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -<span class="hljs-number">20</span>).isActive = <span class="hljs-literal">true</span>

        submitButton.translatesAutoresizingMaskIntoConstraints = <span class="hljs-literal">false</span>
        submitButton.topAnchor.constraint(equalTo: passwordField.bottomAnchor, constant: <span class="hljs-number">8</span>).isActive = <span class="hljs-literal">true</span>
        submitButton.centerXAnchor.constraint(equalTo: passwordField.centerXAnchor).isActive = <span class="hljs-literal">true</span>
        submitButton.widthAnchor.constraint(equalToConstant: <span class="hljs-number">200</span>).isActive = <span class="hljs-literal">true</span>
    }
}
</code></pre>
<p>The first thing I started doing to improve this was to pull the configuration and constraining out to separate functions, so they are a little easier to find/digest when reading. That looks like this:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BasicLayoutViewController</span>: <span class="hljs-title">UIViewController</span> </span>{

    <span class="hljs-keyword">let</span> usernameField = <span class="hljs-type">UITextField</span>()
    <span class="hljs-keyword">let</span> passwordField = <span class="hljs-type">UITextField</span>()
    <span class="hljs-keyword">let</span> submitButton = <span class="hljs-type">UIButton</span>(type: .custom)

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">viewDidLoad</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">super</span>.viewDidLoad()

        configure()
        constrain()
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">()</span></span> {
        view.backgroundColor = .secondarySystemBackground

        usernameField.placeholder = <span class="hljs-string">"Username"</span>
        usernameField.borderStyle = .roundedRect

        passwordField.placeholder = <span class="hljs-string">"Password"</span>
        passwordField.textContentType = .password
        passwordField.isSecureTextEntry = <span class="hljs-literal">true</span>
        passwordField.borderStyle = .roundedRect

        submitButton.setTitle(<span class="hljs-string">"Log in"</span>, <span class="hljs-keyword">for</span>: .normal)
        submitButton.backgroundColor = .systemBlue
        submitButton.layer.cornerRadius = <span class="hljs-number">8</span>
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">constrain</span><span class="hljs-params">()</span></span> {
        view.addSubview(usernameField)
        view.addSubview(passwordField)
        view.addSubview(submitButton)

        usernameField.translatesAutoresizingMaskIntoConstraints = <span class="hljs-literal">false</span>
        usernameField.leadingAnchor.constraint(equalTo: passwordField.leadingAnchor).isActive = <span class="hljs-literal">true</span>
        usernameField.trailingAnchor.constraint(equalTo: passwordField.trailingAnchor).isActive = <span class="hljs-literal">true</span>

        passwordField.translatesAutoresizingMaskIntoConstraints = <span class="hljs-literal">false</span>
        passwordField.topAnchor.constraint(equalTo: usernameField.bottomAnchor, constant: <span class="hljs-number">8</span>).isActive = <span class="hljs-literal">true</span>
        passwordField.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = <span class="hljs-literal">true</span>
        passwordField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: <span class="hljs-number">20</span>).isActive = <span class="hljs-literal">true</span>
        passwordField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -<span class="hljs-number">20</span>).isActive = <span class="hljs-literal">true</span>

        submitButton.translatesAutoresizingMaskIntoConstraints = <span class="hljs-literal">false</span>
        submitButton.topAnchor.constraint(equalTo: passwordField.bottomAnchor, constant: <span class="hljs-number">8</span>).isActive = <span class="hljs-literal">true</span>
        submitButton.centerXAnchor.constraint(equalTo: passwordField.centerXAnchor).isActive = <span class="hljs-literal">true</span>
        submitButton.widthAnchor.constraint(equalToConstant: <span class="hljs-number">200</span>).isActive = <span class="hljs-literal">true</span>
    }
}
</code></pre>
<p>I also went through a phase where all my UI elements were <code>lazy</code> and the configuration was done in a closure. I don't do that anymore mostly because I prefer to keep all my configuration code together and to abstract out commonly used configurations, but when I did it looked something like this:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">private</span> <span class="hljs-built_in">lazy</span> <span class="hljs-keyword">var</span> usernameField: <span class="hljs-type">UITextField</span> = {
    <span class="hljs-keyword">let</span> textField = <span class="hljs-type">UITextField</span>()
    textField.placeholder = <span class="hljs-string">"Username"</span>
    textField.borderStyle = .roundedRect
    <span class="hljs-keyword">return</span> textField
}()
</code></pre>
<h2 id="moving-out-of-the-view-controller">Moving Out Of The View Controller</h2>
<p>The next step was hearing in some conference talk that you should do the view stuff in the view, not the view controller. That made a lot of sense to me, so I started defining custom views like this:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BasicLayoutView</span>: <span class="hljs-title">UIView</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> usernameField = <span class="hljs-type">UITextField</span>()
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> passwordField = <span class="hljs-type">UITextField</span>()
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> submitButton = <span class="hljs-type">UIButton</span>(type: .custom)

    <span class="hljs-keyword">override</span> <span class="hljs-keyword">init</span>(frame: <span class="hljs-type">CGRect</span>) {
        <span class="hljs-keyword">super</span>.<span class="hljs-keyword">init</span>(frame: frame)

        configure()
        constrain()
    }

    <span class="hljs-keyword">required</span> <span class="hljs-keyword">init</span>?(coder: <span class="hljs-type">NSCoder</span>) {
        <span class="hljs-built_in">fatalError</span>(<span class="hljs-string">"init(coder:) has not been implemented"</span>)
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">()</span></span> {
        backgroundColor = .secondarySystemBackground

        usernameField.placeholder = <span class="hljs-string">"Username"</span>
        usernameField.borderStyle = .roundedRect

        passwordField.placeholder = <span class="hljs-string">"Password"</span>
        passwordField.textContentType = .password
        passwordField.isSecureTextEntry = <span class="hljs-literal">true</span>
        passwordField.borderStyle = .roundedRect

        submitButton.setTitle(<span class="hljs-string">"Log in"</span>, <span class="hljs-keyword">for</span>: .normal)
        submitButton.backgroundColor = .systemBlue
        submitButton.layer.cornerRadius = <span class="hljs-number">8</span>
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">constrain</span><span class="hljs-params">()</span></span> {
        addSubview(usernameField)
        addSubview(passwordField)
        addSubview(submitButton)

        usernameField.translatesAutoresizingMaskIntoConstraints = <span class="hljs-literal">false</span>
        usernameField.leadingAnchor.constraint(equalTo: passwordField.leadingAnchor).isActive = <span class="hljs-literal">true</span>
        usernameField.trailingAnchor.constraint(equalTo: passwordField.trailingAnchor).isActive = <span class="hljs-literal">true</span>

        passwordField.translatesAutoresizingMaskIntoConstraints = <span class="hljs-literal">false</span>
        passwordField.topAnchor.constraint(equalTo: usernameField.bottomAnchor, constant: <span class="hljs-number">8</span>).isActive = <span class="hljs-literal">true</span>
        passwordField.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = <span class="hljs-literal">true</span>
        passwordField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: <span class="hljs-number">20</span>).isActive = <span class="hljs-literal">true</span>
        passwordField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -<span class="hljs-number">20</span>).isActive = <span class="hljs-literal">true</span>

        submitButton.translatesAutoresizingMaskIntoConstraints = <span class="hljs-literal">false</span>
        submitButton.topAnchor.constraint(equalTo: passwordField.bottomAnchor, constant: <span class="hljs-number">8</span>).isActive = <span class="hljs-literal">true</span>
        submitButton.centerXAnchor.constraint(equalTo: passwordField.centerXAnchor).isActive = <span class="hljs-literal">true</span>
        submitButton.widthAnchor.constraint(equalToConstant: <span class="hljs-number">200</span>).isActive = <span class="hljs-literal">true</span>
    }
}
</code></pre>
<p>And the view controller would load the view like this:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BasicLayoutViewController</span>: <span class="hljs-title">UIViewController</span> </span>{

    <span class="hljs-comment">// the contentView is lazily loaded so that all the work of initializing our view</span>
    <span class="hljs-comment">// isn't done until the view controller is actually ready to load it</span>
    <span class="hljs-built_in">lazy</span> <span class="hljs-keyword">var</span> contentView: <span class="hljs-type">BasicLayoutView</span> = .<span class="hljs-keyword">init</span>()

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">loadView</span><span class="hljs-params">()</span></span> {
        view = contentView
    }
}
</code></pre>
<p>This works pretty well. It gets the layout into the view, and keeps the view controller pretty clean so that it is easier to find/read/add business logic there. One thing I didn't like though was that I had to provide the <code>required init?(coder:)</code> in all of my views, even though I wasn't using it for anything. It just cluttered up my code and added boilerplate. So I decided to add a new <code>UIView</code> subclass to act as the parent for classes that I intended to layout programmatically. It looks like this:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProgrammaticView</span>: <span class="hljs-title">UIView</span> </span>{
    <span class="hljs-meta">@available</span>(*, unavailable, message: <span class="hljs-string">"Don't use init(coder:), override init(frame:) instead"</span>)
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">init</span>?(coder: <span class="hljs-type">NSCoder</span>) {
        <span class="hljs-built_in">fatalError</span>(<span class="hljs-string">"init(coder:) has not been implemented"</span>)
    }

    <span class="hljs-keyword">override</span> <span class="hljs-keyword">init</span>(frame: <span class="hljs-type">CGRect</span>) {
        <span class="hljs-keyword">super</span>.<span class="hljs-keyword">init</span>(frame: frame)

        configure()
        constrain()
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">()</span></span> {}
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">constrain</span><span class="hljs-params">()</span></span> {}
}
</code></pre>
<p>Marking the required initializer as unavailable makes it so that you don't have to provide it in subclasses. You can see that I've also defined some template methods for <code>configure()</code> and <code>constrain()</code> and called those in the initalizer. That makes <code>BasicLayoutView</code> look like this:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BasicLayoutView</span>: <span class="hljs-title">ProgrammaticView</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> usernameField = <span class="hljs-type">UITextField</span>()
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> passwordField = <span class="hljs-type">UITextField</span>()
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> submitButton = <span class="hljs-type">UIButton</span>(type: .custom)

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">()</span></span> {
        <span class="hljs-comment">// same as before...</span>
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">constrain</span><span class="hljs-params">()</span></span> {
        <span class="hljs-comment">// same as before...</span>
    }
}
</code></pre>
<p>Much nicer. The view that it inherits from tells you that it is intended to be laid out programmatically, and all the code written in this class is relevant to it. You just have to add <code>override</code> to the configure and constrain methods.</p>
<h2 id="cleaning-up">Cleaning Up</h2>
<p>Next, I learned about <code>NSLayout.activate()</code> which takes an array of <code>NSLayoutConstraints</code> and activates them all at once. This might be more efficient in certain cases, but it mostly means that you don't have to write <code>.isActive = true</code> for every constraint. I also added a couple of convenience extensions to <code>UIView</code> to redeuce the number of lines I have to write for every view. That looks like this:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">UIView</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">addConstrainedSubview</span><span class="hljs-params">(<span class="hljs-number">_</span> view: UIView)</span></span> {
        view.translatesAutoresizingMaskIntoConstraints = <span class="hljs-literal">false</span>
        addSubview(view)
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">addConstrainedSubviews</span><span class="hljs-params">(<span class="hljs-number">_</span> views: UIView...)</span></span> {
        views.forEach { view <span class="hljs-keyword">in</span> addConstrainedSubview(view) }
    }
}

<span class="hljs-comment">// in BasicLayoutView</span>
<span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">constrain</span><span class="hljs-params">()</span></span> {
    addConstrainedSubviews(usernameField, passwordField, submitButton)

    <span class="hljs-type">NSLayoutConstraint</span>.activate([
        usernameField.leadingAnchor.constraint(equalTo: passwordField.leadingAnchor),
        usernameField.trailingAnchor.constraint(equalTo: passwordField.trailingAnchor),

        passwordField.topAnchor.constraint(equalTo: usernameField.bottomAnchor, constant: <span class="hljs-number">8</span>),
        passwordField.centerYAnchor.constraint(equalTo: centerYAnchor),
        passwordField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: <span class="hljs-number">20</span>),
        passwordField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -<span class="hljs-number">20</span>),

        submitButton.topAnchor.constraint(equalTo: passwordField.bottomAnchor, constant: <span class="hljs-number">8</span>),
        submitButton.centerXAnchor.constraint(equalTo: passwordField.centerXAnchor),
        submitButton.widthAnchor.constraint(equalToConstant: <span class="hljs-number">200</span>),
    ])
}
</code></pre>
<p>That is both shorter and a little bit easier to read. Again, putting the information that is relevant to this view in the view and striping out noise that we don't really care about.</p>
<p>Next, I started throwing <code>UIStackView</code>s into the mix whenever I could. I added another convenience method specific to stack views too. For this view that would look like this:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">UIStackView</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">addArrangedSubviews</span><span class="hljs-params">(<span class="hljs-number">_</span> views: UIView...)</span></span> {
        views.forEach { view <span class="hljs-keyword">in</span> addArrangedSubview(view) }
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StackViewLayoutView</span>: <span class="hljs-title">ProgrammaticView</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> stackView = <span class="hljs-type">UIStackView</span>()
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> usernameField = <span class="hljs-type">UITextField</span>()
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> passwordField = <span class="hljs-type">UITextField</span>()
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> submitButton = <span class="hljs-type">UIButton</span>(type: .custom)

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">()</span></span> {
        <span class="hljs-comment">// same stuff as before...</span>

        stackView.axis = .vertical
        stackView.spacing = <span class="hljs-number">8</span>
        stackView.alignment = .center
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">constrain</span><span class="hljs-params">()</span></span> {
        addConstrainedSubview(stackView)
        stackView.addArrangedSubviews(usernameField, passwordField, submitButton)

        <span class="hljs-type">NSLayoutConstraint</span>.activate([
            stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: <span class="hljs-number">20</span>),
            stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -<span class="hljs-number">20</span>),
            stackView.centerYAnchor.constraint(equalTo: centerYAnchor),

            passwordField.widthAnchor.constraint(equalTo: stackView.widthAnchor),
            usernameField.widthAnchor.constraint(equalTo: stackView.widthAnchor),
            submitButton.widthAnchor.constraint(equalToConstant: <span class="hljs-number">200</span>),
        ])
    }
}
</code></pre>
<h2 id="anchorage">Anchorage</h2>
<p>Already that is a lot cleaner than where we started and it is pretty flexible in terms of defining a variety of layouts in a fairly concise way. But I usually go a step farther when I am in a context where I can pull in third party dependencies. My favorite library for working with constraints is called <a target="_blank" href="https://github.com/Rightpoint/Anchorage">Anchorage</a>. It lets you write constraints with operators and provides some convenient proxies for doing things like setting all the sides at once or the size or whatever. It is fast to write and very easy to read. It's main downside is increased compile time, but it isn't really noticeable unless you're in a giant project and <a target="_blank" href="https://github.com/Rightpoint/Anchorage/releases/tag/4.5.0">they are doing work to improve that</a>.</p>
<p>Rewriting our view with Anchorage would look like this:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AnchorageLayoutView</span>: <span class="hljs-title">ProgrammaticView</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> usernameField = <span class="hljs-type">UITextField</span>()
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> passwordField = <span class="hljs-type">UITextField</span>()
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> submitButton = <span class="hljs-type">UIButton</span>(type: .custom)

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">()</span></span> {
        <span class="hljs-comment">// same as before...</span>
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">constrain</span><span class="hljs-params">()</span></span> {
        addSubviews(usernameField, passwordField, submitButton)

        passwordField.centerYAnchor == centerYAnchor
        <span class="hljs-comment">// notice you can constrain the leading and trailing anchors at the same time</span>
        passwordField.horizontalAnchors == horizontalAnchors + <span class="hljs-number">20</span>

        usernameField.bottomAnchor == passwordField.topAnchor - <span class="hljs-number">8</span>
        usernameField.horizontalAnchors == passwordField.horizontalAnchors

        submitButton.topAnchor == passwordField.bottomAnchor + <span class="hljs-number">8</span>
        submitButton.centerXAnchor == passwordField.centerXAnchor
        submitButton.widthAnchor == <span class="hljs-number">200</span>
    }
}
</code></pre>
<p>Like I said, it looks super clean and is very easy to read and write.</p>
<h2 id="putting-it-all-together">Putting It All Together</h2>
<p>Finally, I started to combine Anchorage with stackviews, and compose them both with views defined elsewhere.  This is closest to what my actual method is at this point in time. Basically any time I have a view that can be reused, I'll pull it out to its own <code>ProgrammaticView</code> subclass and then just plug it into views where it is needed like we've been doing with <code>UITextField</code>s and <code>UIButton</code>s in this example layout. With our example layout, that might look something like this:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LoginStackView</span>: <span class="hljs-title">ProgrammaticView</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> stackView = <span class="hljs-type">UIStackView</span>()
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> usernameField = <span class="hljs-type">UITextField</span>()
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> passwordField = <span class="hljs-type">UITextField</span>()
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> submitButton = <span class="hljs-type">UIButton</span>(type: .custom)

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">()</span></span> {
        <span class="hljs-comment">// same as before...</span>
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">constrain</span><span class="hljs-params">()</span></span> {
        addSubview(stackView)
        stackView.addArrangedSubviews(usernameField, passwordField, submitButton)

        stackView.edgeAnchors == edgeAnchors
        passwordField.horizontalAnchors == horizontalAnchors
        usernameField.horizontalAnchors == horizontalAnchors
        submitButton.widthAnchor == <span class="hljs-number">200</span>
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ComposedLayoutView</span>: <span class="hljs-title">ProgrammaticView</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> loginStack = <span class="hljs-type">LoginStackView</span>()

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">configure</span><span class="hljs-params">()</span></span> {
        backgroundColor = .secondarySystemBackground
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">constrain</span><span class="hljs-params">()</span></span> {
        addSubview(loginStack)

        loginStack.horizontalAnchors == horizontalAnchors + <span class="hljs-number">20</span>
        loginStack.centerYAnchor == centerYAnchor
    }
}
</code></pre>
<p>Pulling out the login stack to its own view may or may not be worthwhile. It would depend on how often you foresee using this specific view throughout your app. If you needed to, you could make it more configurable so that it could be reused in a wider variety of use cases. Either way, I think it illustrates the technique well.</p>
<p>Side note, you may be wondering how I pass information back up from all the subviews to the view controller. <a target="_blank" href="https://dilloncodes.com/delegate-pattern-in-swift">My last article on delegation</a> describes exactly my process for that, so I won't go into it here.</p>
<h2 id="wrap-up">Wrap Up</h2>
<p>There you have it. That is basically the journey I took from first learning how to define a constraint in code to how I organize that code today. When you get down to it, what that code is doing is almost exactly the same, but I think the method I have now is easier to read (both for myself and others), it is easier to reuse, and it is faster to get something built because I don't have to remember any of the unimportant noise. I hope it has been helpful to you and maybe given you an idea or two that you can try applying in your own code. If you have questions, or ideas for how I can improve things let me know in the comments!</p>
<p>You can find all the code and the sample project I built for this article at <a target="_blank" href="https://github.com/dillon-mce/ProgrammaticUI">this github repo</a>.</p>
<div class="hn-embed-widget" id="buyacoffee"></div>]]></content:encoded></item><item><title><![CDATA[The Delegate Pattern In Swift]]></title><description><![CDATA[https://youtu.be/UIlQ_FHCx_8
Intro
Delegation is a commonly used pattern in Apple's frameworks, but I find that many people have a hard time wrapping their heads around it when they are first learning iOS development. And it is no wonder because the ...]]></description><link>https://dilloncodes.com/delegate-pattern-in-swift</link><guid isPermaLink="true">https://dilloncodes.com/delegate-pattern-in-swift</guid><category><![CDATA[design patterns]]></category><category><![CDATA[Swift]]></category><category><![CDATA[iOS]]></category><category><![CDATA[ios app development]]></category><dc:creator><![CDATA[Dillon McElhinney]]></dc:creator><pubDate>Thu, 31 Dec 2020 02:35:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1609458274409/CoOV2wgbC.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/UIlQ_FHCx_8">https://youtu.be/UIlQ_FHCx_8</a></div>
<h2 id="intro">Intro</h2>
<p>Delegation is a commonly used pattern in Apple's frameworks, but I find that many people have a hard time wrapping their heads around it when they are first learning iOS development. And it is no wonder because the terms used to define it aren't particularly "beginner-friendly". </p>
<p>Take this quote from the <a target="_blank" href="https://docs.swift.org/swift-book/LanguageGuide/Protocols.html#ID276">Swift Language Guide</a>:</p>
<blockquote>
<p>This design pattern is implemented by defining a protocol that encapsulates the delegated responsibilities, such that a conforming type (known as a delegate) is guaranteed to provide the functionality that has been delegated.</p>
</blockquote>
<p>Or this quote from <a target="_blank" href="https://www.swiftbysundell.com/articles/delegation-in-swift/">John Sundell's article on the topic</a>:</p>
<blockquote>
<p>The core purpose of the delegate pattern is to allow an object to communicate back to its owner in a decoupled way. By not requiring an object to know the concrete type of its owner, we can write code that is much easier to reuse and maintain.</p>
</blockquote>
<p>If you have been programming for a while, those sentences probably mean something to you. But if you are just starting out, there's a good chance you just experience some cognitive overload and don't really come away from it knowing anything more about what delegation <em>is</em>. So let's take a look at <strong>what</strong> delegation is, <strong>why</strong> we use it, and an example of <strong>how</strong> I use it in my apps.</p>
<h2 id="what-is-delegation">What Is Delegation?</h2>
<p>The concept itself is fairly simple. I can illustrate it with a metaphor. Imagine an executive has some work they would like done and a few questions they would like answered, but they either can't or won't do those things for themselves. Maybe they don't have the time, or they don't have the skill, or whatever, it doesn't really matter. This person is our "delegator". So what the delegator does is write a job description saying "I need someone who knows how to do this one task and answer these two questions." That person will be the "delegate". The delegate can be anyone in the world, as long as they sign the contract confirming that they know how to do the task and find the answers for the questions. Then, any time the delegator needs that task done or the questions answered, they just hand that off to the delegate who actually does the work and brings the result back.</p>
<p>In Swift, we call the "job description" a protocol. In other languages it is called an interface, so you might see that term as well. According to the <a target="_blank" href="https://docs.swift.org/swift-book/LanguageGuide/Protocols.html">Swift Language Guide</a>: "A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality." Sounds a lot like our job description to me. And when a class <em>adopts</em> a protocol, that is signing the contract. It is telling me and you, and the rest of the code, and the compiler that it can do everything described in that protocol. </p>
<p>So if we write our example scenario as code it might look like this:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// This protocol will hold everything the manager class needs their assistant to do</span>
<span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">ManagerDelegate</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">doLaundry</span><span class="hljs-params">(<span class="hljs-keyword">for</span> manager: Manager, laundry: [Clothes])</span></span> -&gt; [<span class="hljs-type">Clothes</span>]
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">howMuchMoneyDidWeMake</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">Int</span>
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">whatBearIsBest</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">String</span>
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Manager</span> </span>{
    <span class="hljs-comment">// the manager doesn't care what class their assistant is,</span>
    <span class="hljs-comment">// only that they can do what the manager needs them to do</span>
    <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> assistant: <span class="hljs-type">ManagerDelegate?</span> 

    <span class="hljs-comment">// whenever the manager needs those tasks done, they just have the assistant do it</span>
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">performEndOfDayTasks</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">if</span> today.isMonday {
            cleanClothes = assistant.doLaundry(<span class="hljs-keyword">for</span>: <span class="hljs-keyword">self</span>, laundry: dirtyClothes)
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> today.isFriday {
            <span class="hljs-keyword">let</span> profit = assistant.howMuchMoneyDidWeMake()
            tellBoss(<span class="hljs-string">"We made \(profit) dollars this week boss!"</span>)
        }
    }
}

<span class="hljs-comment">// The assistant adopts the protocol and this code will not compile</span>
<span class="hljs-comment">// unless they meet all the requirements of the protocol</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Assistant</span>: <span class="hljs-title">ManagerDelegate</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">doLaundry</span><span class="hljs-params">(<span class="hljs-keyword">for</span> manager: Manager, laundry: [Clothes])</span></span> -&gt; [<span class="hljs-type">Clothes</span>] {
        <span class="hljs-keyword">var</span> cleanClothes = washClothes(laundry) 
        dryClothes(cleanClothes)
        foldClothes(cleanClothes)
        <span class="hljs-keyword">return</span> cleanClothes
    }
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">howMuchMoneyDidWeMake</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">Int</span> {
        <span class="hljs-keyword">return</span> ledger.profit
    }
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">whatBearIsBest</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">String</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"There are basically two schools of thought..."</span>
    }
}
</code></pre>
<h2 id="why-use-delegation">Why Use Delegation?</h2>
<p>So that explains <em>what</em> delegation is and what it might look like, but it doesn't explain <em>why</em> anyone would want to do it in the first place. Why can't the manager just do the work themselves? Like we said earlier, in the real world they might be constrained by time (not enough hours in the day) or constrained by capability (don't have the skill), or they might be constrained by desire (just don't want to do it). When we're talking about code, the metaphor breaks down here a little bit because our code is not (yet) sentient and it doesn't really have any desires. Here are a couple of reasons why we use delegation in our code:
<strong>It promotes proper <em>separation of concerns</em>.</strong> Our <code>Manager</code> class can hold all the logic that is necessary for managing and not have to be cluttered up with logic for assisting. It has an <code>assistant</code> for that. This makes our code easier to read and easier to reason about. When you need to update the managing logic, or you are trying to track down an assisting bug, you know right where to look.
<strong>It <em>decouples</em> our code.</strong> That means each class is not tied to the other directly. We can test each class's logic individually, we can debug them separately, and we can grow one's functionality without affecting the other. And if we want to write a new <code>ExecutiveAssistant</code> class, we can do that and swap it out without affecting anything about how the <code>Manager</code> operates, as long as it adopts the appropriate protocol(s). If we work on a team, one person can work on the <code>Manager</code> class and another can work on the <code>Assistant</code> class without conflicts. This is what people mean when they say two classes are "loosely coupled". Things would be much more "tightly coupled" if our <code>Manager</code> defined its assistant as <code>var assistant: Assistant</code>. In our real-world metaphor, loose coupling would be "I need an assistant who fits this job description" and tight coupling would be something like "I need Dwight as my assistant, no one else will do".
<strong>It promotes <em>reuse</em>.</strong> In the real world we can (theoretically) pluck a manager from one position and place them in another, and as long as they know how to manage people in general, it shouldn't matter if they are managing <em>these</em> people or <em>those</em> people. They should continue to thrive. The same can be said for an assistant, whether they are assisting this person or that person. There are similar examples in our code. Think about a table view. There's a lot of logic used for rendering a vertically scrolling table on screen. You have to manage all the cells and it would probably be good if you make sure they are reused so that your app doesn't generate thousands of views for long lists, etc. There is a lot of logic there that you and I don't want to have to write every time we need a table in our app, so it would be great if there was a class that we could reuse for that. The hitch is that the <em>content</em> that we want to display in each table is different. As it happens Apple came up with a solution for us and it is called <code>UITableView</code> (although modern readers may want to prefer <code>UICollectionView</code>), and they used delegation to allow us to define the logic for our individual apps while keeping the logic for rendering tables wrapped up in its own class where it can be reused.
If all those reasons aren't enough for you, I suppose you could write all of your own code without using delegation. You could use other patterns like target-action or callbacks to achieve many of the same things (with other trade-offs). But if you are developing iOS apps, you're going to have to implement at least a few delegates at some point because it is a common pattern in Apple's frameworks, so you may as well get comfortable with the pattern.</p>
<h2 id="how-i-use-delegation">How I Use Delegation</h2>
<p>I have been developing iOS apps for a few years, so I have implemented my fair share of <code>UITableViewDelegate</code>s and <code>UICollectionViewDataSource</code>s but I also like using the delegate pattern with my own views. I tend to lay out my UI in a <code>UIView</code> subclass, define a delegate for that view, and then pass up any events that happen to the delegate. So if there is a button in the view, the user's action of tapping on that button that gets passed to the delegate (most often its owning <code>UIViewController</code>). It usually looks something like this:</p>
<p>First, we define the delegate protocol. This will be all the messages our view needs to relay to its delegate. For this example, we'll just let them know that the button has been tapped, but in production code you would probably want to have a more specific and communicative name for this function.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">SomeDetailViewDelegate</span>: <span class="hljs-title">AnyObject</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didTapButton</span><span class="hljs-params">()</span></span>
}
</code></pre>
<p>Then the view itself. It has a delegate which is <code>weak</code>. The reason why is beyond the scope of this article, but the simple answer is it helps to prevent retention cycles by making sure <code>SomeDetailView</code> doesn't retain it's delegate. If you want, you can <a target="_blank" href="https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html#ID52">read more about how that works here</a>. Also notice that we marked our protocol has being <code>AnyObject</code>. That is because the compiler won't let us mark it as <code>weak</code> in the view if it isn't known to be a reference type and that is exactly what <code>AnyObject</code> does.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SomeDetailView</span>: <span class="hljs-title">UIView</span> </span>{

    <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> delegate: <span class="hljs-type">SomeDetailViewDelegate?</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> button = <span class="hljs-type">UIButton</span>()

    <span class="hljs-comment">// somewhere in the configuration and layout of button and other views</span>
    button.addTarget(<span class="hljs-keyword">self</span>, action: #selector(didTapButton), <span class="hljs-keyword">for</span>: .touchUpInside)

    <span class="hljs-meta">@objc</span> <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didTapButton</span><span class="hljs-params">(<span class="hljs-number">_</span> sender: UIButton)</span></span> {
        delegate?.didTapButton()
    }
}
</code></pre>
<p>You can see that the view itself is pretty dumb (meaning that it doesn't hold much, if any, business logic). It just defines what the UI looks like and then passes the messages it gets along to its delegate.</p>
<p>Next we have the view controller that will hold this view. It has a strong reference to the view and it is responsible for putting it on screen (not shown here). At some point, preferably before it is possible for the view to send any messages to its delegate, the view controller sets itself as the view's delegate.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SomeDetailViewController</span>: <span class="hljs-title">UIViewController</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-built_in">lazy</span> <span class="hljs-keyword">var</span> detailView: <span class="hljs-type">SomeDetailView</span> = {
        <span class="hljs-keyword">let</span> detailView = <span class="hljs-type">SomeDetailView</span>()
        detailView.delegate = <span class="hljs-keyword">self</span>
        <span class="hljs-keyword">return</span> detailView
    }()
}
</code></pre>
<p>Now to get that to compile, our view controller must adopt the delegate protocol. Here, I am doing it in an extension. This is not required, but it is a fairly common pattern that you'll see and it is a nice way to wrap up all the logic relating to that protocol in one place. It can even be put in a separate file if you want. Regardless of how you conform to the protocol, this is where the logic for what to <em>do</em> when the user taps that button lives.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">SomeDetailViewController</span>: <span class="hljs-title">SomeDetailViewDelegate</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didTapButton</span><span class="hljs-params">()</span></span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Do something when the user taps the button"</span>)
    }
}
</code></pre>
<p>This organization leads to all of the benefits discussed earlier. It separates the UI logic from the business logic, so if you have a layout bug you know where to look. It decouples our view from our view controller, allowing you to make changes to the view with no effect on the business logic and vice versa. And it promotes reuse. Any view controller (or even another view) could now plug this view in and all it needs to do is decide how to conform to the one new protocol method. And, if it is done consistently throughout a code base, anyone else can hop in to our code and know where to look with only a few minutes of explanation.</p>
<h2 id="wrap-up">Wrap Up</h2>
<p>So quick recap:</p>
<ul>
<li>Delegation is just handing off some work to someone/something else who is able to do that work.</li>
<li>Use delegation to separate concerns, to decouple your classes, and to make your code more reusable</li>
<li>A good place to start with delegation is between your view and your view controller </li>
</ul>
<p>I hope this helps, especially if you're just getting started in iOS development. And if it did, let me know! Maybe you're a more experienced developer who thinks the delegate pattern is boring, or outdated, and we shouldn't use it anymore. Feel free to let me know all your thoughts and opinions too.</p>
<div class="hn-embed-widget" id="buyacoffee"></div>]]></content:encoded></item><item><title><![CDATA[Algorithms in Swift - Binary v. Simple Search]]></title><description><![CDATA[I know algorithms often feel like an academic topic – something for nerds in computer science classes. They can be intimidating because their explanations and examples tend to be math-heavy and there is a lot of hard-to-decipher jargon out there abou...]]></description><link>https://dilloncodes.com/algorithms-in-swift-binary-v-simple-search</link><guid isPermaLink="true">https://dilloncodes.com/algorithms-in-swift-binary-v-simple-search</guid><category><![CDATA[Swift]]></category><category><![CDATA[algorithms]]></category><dc:creator><![CDATA[Dillon McElhinney]]></dc:creator><pubDate>Mon, 30 Nov 2020 00:35:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1606695960424/4hflSWM4h.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I know algorithms often feel like an academic topic – something for nerds in computer science classes. They can be intimidating because their explanations and examples tend to be math-heavy and there is a lot of hard-to-decipher jargon out there about them. But they are really useful for solving interesting problems, and knowing how to pick the right algorithm for the problem at hand can make you seem like a programming genius! In this post, I want to talk about algorithms in general, take a look at the binary search algorithm, and examine how to think about the complexity of an algorithm.</p>
<h3 id="algorithms">Algorithms</h3>
<p><a target="_blank" href="https://www.google.com/search?q=algorithm">If you search Google</a>, you’ll find that an algorithm is “a process or set of rules to be followed in calculations or other problem-solving operations, especially by a computer.” You use algorithms to search and to sort. You use algorithms to find the shortest route between two points, or to determine the next best move to take in a game. They are basically a set of instructions that you give to the computer that allows it to accomplish a task. And I know what you’re thinking, that is <em>all code</em>. And as far as I know, that is technically true. At least according to the definition given above. But when we talk about algorithms, we are usually talking about solving <em>interesting</em> problems. These are problems that seem easy for a human to do (with a small number of inputs at least) but whose solutions are not straightforward to describe in a way that computers can understand.</p>
<p>Algorithms tend to be described as <em>elegant</em> or <em>clever</em>. They don’t list out explicitly all the steps that a computer would use to solve the problem, they are distilled down descriptions of how to solve the problem. You and I are unlikely to ever come up with one that wasn’t already described by some computer science professor or medieval Muslim. <a target="_blank" href="https://en.wikipedia.org/wiki/Timeline_of_algorithms">Basically all of the good and useful ones have already been thought of</a>, so our job when it comes to algorithms tends to be <em>finding</em> the right one and <em>adapting</em> it to solve our particular problem. Even so, it is a helpful exercise to go through trying to come up with them for yourself, as it will help you remember the algorithms when the time comes that you need to implement them in real code.</p>
<h3 id="binary-search">Binary Search</h3>
<p>Binary search is a relatively simple algorithm that can save <strong>huge</strong> amounts of time/calculations when you are searching against a large sorted list. Here is an example of what this algorithm might look like in real life (or at least what it would have looked like before we had digital dictionaries): Suppose you were reading an article or book and came across the word “obeliscolychny”. If you are like me, you probably wouldn’t know what that word means, and you would probably even have difficulty parsing out any meaning from the roots by just looking at it. So you, like any good reader, would pull out your dictionary and look up the word.</p>
<p>How would you go about doing that? Would you start at the first page and flip through them one at a time, scanning each page until you found the word? No! You would crack it open in the middle, check what letter you landed on, and move backwards if you had gone too far or forward if you had not gone far enough. You would quickly narrow down your scope until you found the word you were looking for. (It means “lighthouse” if you haven’t already Googled it.)</p>
<p>That’s how binary search works. It halves the number of possible elements with each step by checking the middle element. If it is too high, it can safely disregard all the elements above that element. If it is too low, it can safely disregard all the elements below that element. (This is why it only works if you are searching a sorted list.) It keeps doing that until finds the element it is looking for. Here is an example of what it might look like in Swift:</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">binarySearch</span>&lt;T: Comparable&gt;<span class="hljs-params">(<span class="hljs-number">_</span> array: [T], <span class="hljs-keyword">for</span> element: T)</span></span> -&gt; <span class="hljs-type">Int?</span> {
    <span class="hljs-keyword">var</span> low = <span class="hljs-number">0</span>
    <span class="hljs-keyword">var</span> high = array.<span class="hljs-built_in">count</span> - <span class="hljs-number">1</span>

    <span class="hljs-keyword">while</span> low &lt;= high {
        <span class="hljs-keyword">let</span> mid = (low + high) / <span class="hljs-number">2</span>
        <span class="hljs-keyword">let</span> guess = array[mid]

        <span class="hljs-keyword">if</span> guess == element { <span class="hljs-keyword">return</span> mid }
        <span class="hljs-keyword">if</span> guess &gt; element {
            high = mid - <span class="hljs-number">1</span>
        } <span class="hljs-keyword">else</span> {
            low = mid + <span class="hljs-number">1</span>
        }
    }
    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}
</code></pre>
<p>First, I make variables to keep track of the low and high spots to check against. Then I make a while loop that continues as long as <code>low</code> is less than or equal to <code>high</code>  (meaning we still have elements to check). Each time through the loop, I get the midpoint between <code>low</code> and <code>high</code> and get the guess associated with that midpoint. If <code>guess</code> is the element we’re looking for, I return it. Otherwise, if <code>guess</code> is greater than the element we’re searching for (meaning it is too high) we set <code>high</code> equal to the current midpoint minus 1. If <code>guess</code> is less than the element (meaning it is too low) we set <code>low</code> equal to the current midpoint plus 1. If we get to the point were <code>low</code> is no longer less than or equal to <code>high</code>, it means the element doesn’t exist in this array, so I return nil.</p>
<p>In comparison, a “simple search” might look like this:</p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">simpleSearch</span>&lt;T: Equatable&gt;<span class="hljs-params">(<span class="hljs-number">_</span> array: [T], <span class="hljs-keyword">for</span> element: T)</span></span> -&gt; <span class="hljs-type">Int?</span> {    
    <span class="hljs-keyword">for</span> (index, item) <span class="hljs-keyword">in</span> array.enumerated() {
        <span class="hljs-keyword">if</span> item == element { <span class="hljs-keyword">return</span> index }
    }
    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}
</code></pre>
<p>As you can see, it is much less code. Fewer places for errors. It just starts at the beginning and moves forward until it finds the element it is looking for. If it gets all the way through and doesn’t find it, it returns <code>nil</code>. It may be a better option, or at least it may not make a significant difference, if you know for certain that there will be a relatively small number of elements you are searching against.</p>
<p>To test these functions (and illustrate how many steps they take) I made a little list and searched for an element on it using both functions:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">var</span> list: [<span class="hljs-type">Int</span>] = []
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">stride</span>(from: <span class="hljs-number">1</span>, to: <span class="hljs-number">100</span>, by: <span class="hljs-number">2</span>) {
    list.append(i)
}

binarySearch(list, <span class="hljs-keyword">for</span>: <span class="hljs-number">99</span>)
simpleSearch(list, <span class="hljs-keyword">for</span>: <span class="hljs-number">99</span>)
</code></pre>
<p>Then I added in a couple of helper variables and printed these results out to the console:</p>
<pre><code><span class="hljs-attribute">Binary</span> search took <span class="hljs-number">6</span> steps and completed in <span class="hljs-number">3</span>.<span class="hljs-number">898143768310547</span>e-<span class="hljs-number">05</span> seconds.
<span class="hljs-attribute">Simple</span> search took <span class="hljs-number">50</span> steps and completed in <span class="hljs-number">7</span>.<span class="hljs-number">200241088867188</span>e-<span class="hljs-number">05</span> seconds.
</code></pre><p>Binary search takes about 0.000039 seconds and simple search takes about 0.000072. With so few elements, binary search is only slightly faster in terms of time (and basically imperceptibly), but you can see that the number of steps is way lower with binary search. We’ll look at what a difference this can mean when you have way more inputs in the next section.</p>
<h3 id="complexity">Complexity</h3>
<p>If you were only looking at the times above, you might guess that simple search takes twice as long as binary search. So if we were to search a list of 500,000 elements instead of 50, however long binary search took, simple search would take twice as long. Let’s look at how long it actually takes:</p>
<pre><code><span class="hljs-attribute">Binary</span> search took <span class="hljs-number">19</span> steps and completed in <span class="hljs-number">6</span>.<span class="hljs-number">508827209472656</span>e-<span class="hljs-number">05</span> seconds.
<span class="hljs-attribute">Simple</span> search took <span class="hljs-number">500000</span> steps and completed in <span class="hljs-number">0</span>.<span class="hljs-number">1531449556350708</span> seconds.
</code></pre><p>Binary search didn’t even double in the amount of time it takes (although the number of steps more than doubled, I’m assuming that a chunk of the time it takes has to do with setting up the objects in memory, not the actual calculations), but simple search takes more than 2000 times as long as it did the first time. You can also see that the number of steps necessary for simple search is <em>way</em> higher than binary. So it is not just that simple search takes longer than binary, but that the number of calculations it takes to complete (in the worst case) grows at a different rate as a function of the number of inputs.</p>
<p>Let’s break that down. In the first example above, simple search took 50 steps to find the last element in a list of 50 items. In the second example, it took 500,000 steps to find the last element in a list of 500,000 items. This means that the number of calculations required in the worst case is the same as the number of inputs given. The way you’ll see this described when talking about algorithms is its complexity or its speed or its running time and it is communicated with <a target="_blank" href="https://en.wikipedia.org/wiki/Big_O_notation">Big O notation</a> (insert sex joke here). The big O notation for simple search is <code>O(n)</code> because the number of calculations required is the same as the number of inputs (<code>n</code> stands for the number of inputs).</p>
<p>So what about Binary search? You don’t necessarily have to understand the math behind it, but since we are cutting the elements in half with each step, the number of calculations required in the worst case is log base2 of the number of inputs (rounded up to the nearest integer because you can’t have half a calculation). For instance log(50) is approximately 5.6 and so it took 6 steps when we had 50 items. log(500000) is about 18.9 and it took 19 steps when we had 500,000 items. So the big O notation for binary search is <code>O(log(n))</code>.</p>
<p>It is important to understand the complexity of the algorithm you are using if you are going to be calling it with more than a few inputs. Even algorithms with the worst complexity work ok with a small number of inputs, but many of them grow at a rate much faster than you might think. It is easy to write code that works with your sample data, but is totally unusable when it comes into contact with real-world data that is often a much larger set.</p>
<p>There are a few other common big O notations that you’ll see associated with algorithms:<code>O(1)</code>, <code>O(n * log(n))</code>, <code>O(n^2)</code>, <code>O(2^n)</code> and  <code>O(n!)</code>. The best is <code>O(1)</code> or <em>constant time</em> which means that it takes the same amount of time no matter how many inputs there are. One example of this in Swift is checking whether an element is contained in a <code>Set</code>. The next is <code>O(log(n))</code> or <em>log time</em> which grows very slowly. Binary search is a good example of this. Next is <code>O(n)</code> or <em>linear time</em> which grows just as fast as the number of inputs. We saw this with simple search. Next is <code>O(n * log(n))</code>  or <em>log-linear time</em>. A good example of this is a the quicksort algorithm (which maybe I’ll cover in another post). Next is <code>O(n^2)</code> or <em>quadratic time</em>. An example of this is selection sort (again, I’ll link to this after I write about it). After that is <code>O(2^n)</code> or <em>exponential time</em>. One example of this is bogo sort (also known as permutation sort). Finally we have <code>O(n!)</code> or <em>factorial time</em>. An example of this is the algorithm to solve the <a target="_blank" href="https://en.wikipedia.org/wiki/Travelling_salesman_problem">traveling salesman problem</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606601227504/mkfeJX2sl.png" alt="Big-O-Graph.png" /></p>
<h3 id="wrap-up">Wrap Up</h3>
<p>I hope this has been helpful. I know I learned a lot in writing it. Quick recap:</p>
<ul>
<li>Algorithms are condensed descriptions of how to solve a particular problem, especially for computers.</li>
<li>Binary search is faster than simple search, but requires a sorted list and a little more code.</li>
<li>The complexity, or the rate of the number of necessary calculations as a function of the number of inputs, of a function can be described with big O notation.</li>
</ul>
<p>What do you think? What algorithms have you used to solve real-world problems? How has understanding them made you a better developer? Or has it?</p>
<hr />
<p>P.S. I am (like everyone) both a human and still learning, so you find any mistakes in my writing or have suggestions for things to add, let me know!</p>
<div class="hn-embed-widget" id="buyacoffee"></div>]]></content:encoded></item></channel></rss>