<?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" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Kari Grooms on Medium]]></title>
        <description><![CDATA[Stories by Kari Grooms on Medium]]></description>
        <link>https://medium.com/@kagrooms_45877?source=rss-4af315f40d50------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*wa1CIhY-D1ik825Fqoql4g.jpeg</url>
            <title>Stories by Kari Grooms on Medium</title>
            <link>https://medium.com/@kagrooms_45877?source=rss-4af315f40d50------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 12:20:02 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@kagrooms_45877/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[ObservableObject, @Published and Protocols with SwiftUI, UIKit and Cuckoo]]></title>
            <link>https://medium.com/expedia-group-tech/observableobject-published-and-protocols-with-swiftui-uikit-and-cuckoo-cce69a47f08a?source=rss-4af315f40d50------2</link>
            <guid isPermaLink="false">https://medium.com/p/cce69a47f08a</guid>
            <category><![CDATA[combine]]></category>
            <category><![CDATA[mobile-development]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[Kari Grooms]]></dc:creator>
            <pubDate>Tue, 04 Jan 2022 14:02:46 GMT</pubDate>
            <atom:updated>2022-12-14T22:44:09.493Z</atom:updated>
            <content:encoded><![CDATA[<h4><a href="http://medium.com/expedia-group-tech/engineering/home">EXPEDIA GROUP TECHNOLOGY — ENGINEERING</a></h4><h3>ObservableObject, @Published, and Protocols with SwiftUI, UIKit, and Cuckoo</h3><h4>How to use Combine and protocols in any view</h4><figure><img alt="Woman using coin binoculars at the Empire State Building in New York City." src="https://cdn-images-1.medium.com/max/800/1*u4E80O95VylXfXwByVqUNQ.jpeg" /><figcaption>Photo by Frederick Marschall on Unsplash</figcaption></figure><p>ObservableObject was introduced as part of Apple®️’s Combine framework and is foundational to data flow in SwiftUI. Extending protocols with ObservableObject, however, is not as straightforward. In this article, we explore patterns for leveraging protocols with ObservableObject in both SwiftUI and UIKit and the challenges encountered along the way.</p><h3>ObservableObject usage in SwiftUI</h3><p>Let’s take a look at the typical usage of <a href="https://developer.apple.com/documentation/combine/observableobject">ObservableObject</a> in SwiftUI. For our example, we create an ObservableObject that handles fetching pricing data.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/46327d6cb3b647b43e199a08228bfa85/href">https://medium.com/media/46327d6cb3b647b43e199a08228bfa85/href</a></iframe><p>For our example view, we display the price, if we have it; otherwise, we display a fallback message. We provide a button to initiate the pricing request for demonstration purposes.</p><figure><img alt="A simple view that displays “Price not available” if we do not have any pricing data to show. A “Fetch price” button is provided to initiate a request for pricing data for demo purposes." src="https://cdn-images-1.medium.com/max/165/1*SraQRCkK81p-gLygz_6dlw.png" /></figure><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/56c1ee49f258d1ffb9f9e5517682225a/href">https://medium.com/media/56c1ee49f258d1ffb9f9e5517682225a/href</a></iframe><h3>Defining a protocol extending ObservableObject</h3><p>Let’s see how we could leverage a protocol for the PricingProvider.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a2ff602d7f5dcdf20cab2a67893ec89c/href">https://medium.com/media/a2ff602d7f5dcdf20cab2a67893ec89c/href</a></iframe><p>This looks good, but we are missing a very key characteristic of an ObservableObject. We need at least one @Publishedproperty or we need to explicitly call self.objectWillChange.send() so that our object can notify subscribers of its updates. Without these, our views will never display an updated price, as shown below.</p><figure><img alt="Animated gif showing that the “Fetch price” button is being tapped, but the PriceView is not displaying a new price." src="https://cdn-images-1.medium.com/max/392/1*m8-49pAsh2xjVUCHm-noGQ.gif" /></figure><h3>@Published properties and protocols</h3><p>Let’s try adding the @Publishedproperty requirement to our PriceRequesting protocol.</p><figure><img alt="This image shows the PriceRequesting protocol with the @Published annotation added to the price property. Xcode is displaying an error message: “Property ‘price’ declared inside a protocol cannot have a wrapper”." src="https://cdn-images-1.medium.com/max/1024/1*3cNtfIltYqcxBMarX1_ZgA.png" /></figure><p>Property wrappers cannot be used inside protocols, unfortunately. Without this, we cannot enforce implementation of a publisher. There are ways around this though. @Published wraps our property with Published&lt;Value&gt;, so we can expose some values for these in our protocol.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ecb1b53656e1f74f198457a47faee517/href">https://medium.com/media/ecb1b53656e1f74f198457a47faee517/href</a></iframe><p>Now our view works as expected, receiving updates from PricingProvider, while satisfying the publisher requirements of our protocol. Note that _price exposes the Published&lt;Price?&gt; value and $price exposes the publisher.</p><figure><img alt="Animated gif showing that the “Fetch price” button is being tapped and the displayed price is being updated." src="https://cdn-images-1.medium.com/max/196/1*xtS80bu8n_QYg3xe8ekzFQ.gif" /></figure><h3>Using an extended ObservableObject protocol in SwiftUI</h3><p>Now that our protocol is defined, let’s try and use it in our SwiftUI view.</p><figure><img alt="Image of the PriceView code where we changed “@ObservedObject var pricing: PricingProvider” to “@ObservedObject var pricing: PriceRequesting”. This, however, causes errors: “Protocol ‘PriceRequesting’ as a type cannot conform to ‘ObservableObject’” and “Protocol ‘PriceRequesting’ can only be used as a generic constraint because it has Self or associated type requirements”." src="https://cdn-images-1.medium.com/max/1022/1*3qwSvzYroEh2qw-GRI0Ntg.png" /></figure><p>Because ObservableObject uses Self for its <a href="https://developer.apple.com/documentation/combine/observableobject/objectwillchange-2oa5v">objectWillChange</a> property, we cannot declare it as a type directly. We can work around this though. In order to continue to leverage @ObservedObject, we can make PriceView a generic type.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/070c6557ec0204ffb4aadbc6e4532c98/href">https://medium.com/media/070c6557ec0204ffb4aadbc6e4532c98/href</a></iframe><p>Another option would be to decouple our protocol from ObservableObject altogether. Since we exposed Published&lt;Price?&gt;.Publisher in our protocol, we technically do not need to extend ObservableObject.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5ab91ecd82ce7661929365bf3fe15372/href">https://medium.com/media/5ab91ecd82ce7661929365bf3fe15372/href</a></iframe><p>We will, however, need to update our view to explicitly subscribe to the publisher we defined in our protocol since we can no longer use @ObservedObject.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f16f6594b42072991ca8965d7d58ce34/href">https://medium.com/media/f16f6594b42072991ca8965d7d58ce34/href</a></iframe><p>In order to keep our price updated in the view, we need to create a @State variable for this data. We use <a href="https://developer.apple.com/documentation/swiftui/view/onreceive(_:perform:)">.onReceive(_:perform)</a> to explicitly subscribe to price updates and update our internal @State variable to trigger a re-render of our view.</p><figure><img alt="Animated gif showing that the “Fetch price” button is being tapped and the displayed price is being updated after implementing the PriceRequesting protocol via a @State and .onReceive(_:) implementation." src="https://cdn-images-1.medium.com/max/196/1*1hbSD3D1Fzj9rFB6Y7gQVw.gif" /></figure><h3>Using an extended ObservableObject protocol in UIKit</h3><p>Below is the UIKit implementation of our simple pricing view. As with our SwiftUI example, we will start off using the ObservableObject implementation directly instead of our extended protocol.</p><figure><img alt="Animated gif of the UIKit version of the same pricing view. Shows that when the “Fetch price” button is being tapped, the displayed price is being updated." src="https://cdn-images-1.medium.com/max/196/1*7WxUFxeNrXj5aNvKVfehFA.gif" /><figcaption>UIKit version</figcaption></figure><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/070e9fae206e1ed3d9641b0cd2ba850d/href">https://medium.com/media/070e9fae206e1ed3d9641b0cd2ba850d/href</a></iframe><p>Unlike SwiftUI, there is no built-in mechanism in UIKit for subscribing to updates. Since we always have to explicitly subscribe to a publisher in UIKit, it’s rather simple to update this code to leverage the PriceRequesting protocol instead of the ObservableObject implementation.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b844d58cdb7fb4c80bc1f1aa08c59af6/href">https://medium.com/media/b844d58cdb7fb4c80bc1f1aa08c59af6/href</a></iframe><h3>Mocking ObservableObject with Cuckoo</h3><p>Now that we know several options for leveraging protocols via extendingObservableObject or utilizing Published&lt;Value&gt;, let’s see if they are easily mockable with <a href="https://github.com/Brightify/Cuckoo">Cuckoo</a>.</p><figure><img alt="Image of Xcode showing the MockPriceRequesting class generated by Cuckoo with errors: “Protocol ‘PriceRequesting’ can on be used a generic constraint because it has Self or associated type requirements”." src="https://cdn-images-1.medium.com/max/1024/1*62EkoPhW0poGC8J4NY80QQ.png" /></figure><p>Apparently, they are not. In fact, it’s not currently possible to mock protocols extending ObservableObject in Cuckoo. At the time of writing this, there is an open <a href="https://github.com/Brightify/Cuckoo/issues/381">issue</a> for it. If we want to use Cuckoo to mock our protocol, we have to decouple.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5ab91ecd82ce7661929365bf3fe15372/href">https://medium.com/media/5ab91ecd82ce7661929365bf3fe15372/href</a></iframe><p>Let’s try again.</p><figure><img alt="Image of Xcode showing the PriceRequestingStub class generated by Cuckoo with errors: “Cannot specialize a non-generic definition”." src="https://cdn-images-1.medium.com/max/1024/1*dvNQ4fPlFVCXD4-BiIYgbw.png" /></figure><p>It turns out this new “Cannot specialize a non-generic definition” error is due to both the Foundation and Combine frameworks having a Published type. All of the mocks generated by Cuckoo end up in a single GeneratedMocks.swift file, so if you are using Foundation in another mocked file, then this error will occur.</p><p>There are a couple of options for working around this. We could be explicit in referencing Combine in our protocol.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f51e592c9336915f8c8153e4bdaebdb7/href">https://medium.com/media/f51e592c9336915f8c8153e4bdaebdb7/href</a></iframe><p>Alternatively, we could type erase our publisher via AnyPublisher. To enforce implementation of a publisher in our protocol, we really only need to require a Publisher property and do not need to require a property for the published value (Published&lt;Price?&gt;).</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2a59ce649afd8276e1d2d2f9c46e4466/href">https://medium.com/media/2a59ce649afd8276e1d2d2f9c46e4466/href</a></iframe><h3>Key Takeaways</h3><ul><li>@Published property wrappers are not allowed in protocols. Use AnyPublisher or Published&lt;Value&gt;.Publisher to enforce a publisher implementation.</li><li>Use the _ and $ prefixes to access the value and publisher of an@Published property.</li><li>If you extend ObservableObject, be prepared to make the controllers and/or views (UIKit or SwiftUI) requiring it generic with where clauses.</li><li>You can bypass extending ObservableObject altogether and just require an AnyPublisher in your protocol.</li><li>SwiftUI views can achieve the same functionality as @ObservedObject by using a combination of @State and .onReceive(_:).</li><li>Unlike SwiftUI, subscribing to publishers in UIKit via a protocol or an ObservableObject implementation is done the same way.</li><li>If you want Cuckoo to mock your protocols, then your protocols can’t extend ObservableObject (yet). See Cuckoo <a href="https://github.com/Brightify/Cuckoo/issues/381">issue</a>.</li><li>If using Cuckoo, beware of Published&lt;Value&gt; in your protocols due to conflicts with the Foundation framework. Save yourself the trouble and use AnyPublisher.</li></ul><p>Thanks for reading. 😄</p><blockquote><a href="https://lifeatexpediagroup.com/technology?utm_source=techblog&amp;utm_medium=career&amp;utm_campaign=postfooter">Learn more about technology at Expedia Group</a></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cce69a47f08a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/expedia-group-tech/observableobject-published-and-protocols-with-swiftui-uikit-and-cuckoo-cce69a47f08a">ObservableObject, @Published and Protocols with SwiftUI, UIKit and Cuckoo</a> was originally published in <a href="https://medium.com/expedia-group-tech">Expedia Group Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[SwiftUI with UICollectionView]]></title>
            <link>https://medium.com/expedia-group-tech/swiftui-with-uicollectionview-aba7cbaf6d16?source=rss-4af315f40d50------2</link>
            <guid isPermaLink="false">https://medium.com/p/aba7cbaf6d16</guid>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[ui]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[swiftui]]></category>
            <dc:creator><![CDATA[Kari Grooms]]></dc:creator>
            <pubDate>Thu, 22 Apr 2021 13:03:28 GMT</pubDate>
            <atom:updated>2021-04-22T13:03:28.380Z</atom:updated>
            <content:encoded><![CDATA[<h4><a href="https://medium.com/expedia-group-tech/software/home">EXPEDIA GROUP TECHNOLOGY — SOFTWARE</a></h4><h4>How to bring the two together</h4><figure><img alt="Image of a collection view on the Vrbo iOS app main screen overlay on a code snippet for a SwiftUICollectionViewCell class." src="https://cdn-images-1.medium.com/max/1024/1*IBkSiVf_ir-8yNvTFq8Utg.png" /><figcaption>Image by author</figcaption></figure><p>In my <a href="https://medium.com/expedia-group-tech/lessons-in-swiftui-2a1e1ae03f2a">Lessons in SwiftUI</a><em> </em>blog post, I covered some of the differences between SwiftUI® in iOS 13 vs iOS 14. In iOS 14, we get <a href="https://developer.apple.com/documentation/swiftui/lazyvstack">LazyVStack</a> and <a href="https://developer.apple.com/documentation/swiftui/lazyhstack">LazyHStack</a>, which give us the performance benefits and functionality of <a href="https://developer.apple.com/documentation/uikit/uicollectionview">UICollectionView</a> in UIKit. In the Vrbo™️ (part of Expedia Group™️) iOS app, we use UICollectionView all over the place including the discovery feeds on the home screen, search results, trip boards, etc. Until we deprecate support for iOS 13, these UICollectionViews will be sticking around. There is also a strong possibility that we will be in a hybrid state of UIKit/SwiftUI for a while, so deprecating our use of UICollectionView is not likely to happen all at once even after we drop support for iOS 13.</p><blockquote>Can we use SwiftUI inside UICollectionView?</blockquote><p>Yes…technically. :)</p><p>If you’re looking to start leveraging SwiftUI immediately while still using UICollectionView, it is possible. One approach is to use SwiftUI Views as the content for your <a href="https://developer.apple.com/documentation/uikit/uicollectionviewcell">UICollectionViewCell</a>, which is what I&#39;m going to cover in this post. I will warn you though, it might not always make sense to do this.</p><h3>SwiftUI View inside UICollectionViewCell</h3><p>Let’s first take a look at how to add a SwiftUI View to your UICollectionViewCell.</p><p>This is the SwiftUI View we will be working with for our example:</p><figure><img alt="Sample property card with an image of a luxury condo above some description text about the property and its price." src="https://cdn-images-1.medium.com/max/271/1*qWk7AoAsVXNETRYZydiCOQ.png" /></figure><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1f438cf1a0a64f51fe897a23b298d4f7/href">https://medium.com/media/1f438cf1a0a64f51fe897a23b298d4f7/href</a></iframe><h4>Adding the View</h4><p>Next, we embed the SwiftUI Card inside a UICollectionViewCell.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c17530c2e2e5aacc65fec5b188b61618/href">https://medium.com/media/c17530c2e2e5aacc65fec5b188b61618/href</a></iframe><h3>Integrating with UICollectionView</h3><h4><strong>Configure UICollectionView</strong></h4><p>Here’s a basic UICollectionView setup to render the cell above. To start, we’ll hard-code the number of cells (we will update this later).</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/45bbb537ed8de52b1ee02886d1e4b7c6/href">https://medium.com/media/45bbb537ed8de52b1ee02886d1e4b7c6/href</a></iframe><p>So far, so good.</p><figure><img alt="Screenshot of app displaying a horizontal collection view of the SwiftUI property card" src="https://cdn-images-1.medium.com/max/439/1*0Db8cj9v0XV7g-_vKbZang.png" /></figure><h4>UIHostingController Setup/Cleanup</h4><p>I covered <a href="https://developer.apple.com/documentation/swiftui/uihostingcontroller">UIHostingController</a> in <a href="https://medium.com/expedia-group-tech/lessons-in-swiftui-2a1e1ae03f2a">Lessons in SwiftUI</a>, but I didn&#39;t cover it&#39;s usage outside of a UIViewController, which is what we&#39;re doing here. UIHostingController is a subclass of UIViewController and is how we use SwiftUI inside UIKit. Typically, when we add UIViewControllers to the hierarchy, we don&#39;t just add its UIView as a subview, but also add the actual controller as a child of the UIViewController. If we don&#39;t do this, we may run the risk of some unwanted side effects...memory leaks, views mysteriously disappearing, etc. So while the code above technically works and has been verified to not have any memory leaks, this may or may not cause other unintended issues. To err on the side of caution, let&#39;s go ahead and add it to the stack.</p><p>Here, we’re adding some helper methods to properly add and remove the UIHostingController in our UICollectionViewCell.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e6e921844e9698ff309ecb692a51c624/href">https://medium.com/media/e6e921844e9698ff309ecb692a51c624/href</a></iframe><p>Now, our parent UIViewController can add the UIHostingController as a child.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f1ca97006a22fbc4cc130899b183bcab/href">https://medium.com/media/f1ca97006a22fbc4cc130899b183bcab/href</a></iframe><p>Lets verify clean up happens when we navigate away from the parent UIViewController.</p><figure><img alt="Screenshot of debugger printing out our setup/cleanup messages." src="https://cdn-images-1.medium.com/max/620/1*em9oArVvEq3VA0sOWyDldw.png" /></figure><p>Sweet. 😄</p><h3>Passing Data</h3><p>Up to this point, we’re rendering a SwiftUI View for our UICollectionViewCell content and we&#39;re properly setting this up. Now we need to start passing our SwiftUI View some data, so that each cell&#39;s content is different.</p><p>First, let’s update our SwiftUI View to accept some data.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/52162ab78fa67fc0ff0b28a2a1ae03e6/href">https://medium.com/media/52162ab78fa67fc0ff0b28a2a1ae03e6/href</a></iframe><p>I have gone ahead and made CardContent a protocol here instead of a struct. In the Vrbo iOS app, the data providing the content for these cards is coming from a variety of backend services. We use cards like these for showing recommended properties, recently viewed properties, search results, properties on a trip board, etc. Making this a protocol allows all of these existing data models to simply conform to the protocol instead of creating new data models.</p><p>We have a new problem now though. We need the parent UIViewController to provide data to our UICollectionViewCell, but we won&#39;t have this data on initialization.</p><figure><img alt="Screenshot of Swift code displaying error message: Missing argument for parameter ‘content’ in call" src="https://cdn-images-1.medium.com/max/901/1*hvUr7ShyidcSepM-jxE_yA.png" /></figure><p>We need to refactor our code a bit to account for this.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8a5c4073854388777745a278eb9b006d/href">https://medium.com/media/8a5c4073854388777745a278eb9b006d/href</a></iframe><p>A few things to note:</p><ul><li>embed(in parent: UIViewController) has been updated to also accept the data for our card.</li><li>Instead of setting up UIHostingController and adding the view on initialization, we&#39;ve now moved this logic to the embed() method.</li><li>If the UIHostingController has already been initialized, we update the host&#39;s rootView with a new Card instead of spinning up another UIHostingController and having to re-add the view to the cell.</li><li>We’re no longer using constraints to layout the SwiftUI Card. This isn&#39;t required, but is another alternative to laying out an embedded SwiftUI View.</li></ul><p>Our last step is to pass the data from our parent UIViewController to the cell, so we’ll add some fixture data for our example and then update our UICollectionViewDataSource methods to pass this data along.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b0e819018281dcffa443911d0beefc52/href">https://medium.com/media/b0e819018281dcffa443911d0beefc52/href</a></iframe><p>We can optimize this even further and make a SwiftUICollectionViewCell so that this is reusable.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d435f909d5ae7b4789798b6a910dd171/href">https://medium.com/media/d435f909d5ae7b4789798b6a910dd171/href</a></iframe><p>Now we can make MyCollectionViewCella subclass of SwiftUICollectionViewCell.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/10755c12863e2158787af222ab91bf32/href">https://medium.com/media/10755c12863e2158787af222ab91bf32/href</a></iframe><p>And then update our parent UIViewController to call our new .configure() method.</p><pre><strong>let</strong> item = items[indexPath.row]<br>cell.configure(with: item, parent: <strong>self</strong>)<br>// cell.embed(in: self, withContent: item)<br><strong>return</strong> cell</pre><p>Alright! Here’s what we’ve got in our UI now.</p><figure><img alt="Animated gif scrolling through the collection view; showing different property cards" src="https://cdn-images-1.medium.com/max/438/1*FBiTUW97Otlbb4fTfIamWA.gif" /></figure><h3>Layout Considerations</h3><p>We’re getting pretty close here, but have a couple of things we could improve in the UI. For instance, we want our images to be the same size, so that the content under each image also lines up with the rest of the cards.</p><figure><img alt="Screenshot of collection view showing that the images in our collection view are not all the same size." src="https://cdn-images-1.medium.com/max/439/1*tgIpJH5nmtkXfFF_MqWw5Q.png" /></figure><p>The first thing to note is that we’re sizing our cells via the UICollectionViewDelegateFlowLayout, so our SwiftUI Card needs to support its parent container determining its size.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f3c1bacdc56cc1d4c61bd112215f147c/href">https://medium.com/media/f3c1bacdc56cc1d4c61bd112215f147c/href</a></iframe><p>In its current form, our SwiftUI Card doesn&#39;t do a very good job of adapting to its size. Our images run the risk of being skewed.</p><figure><img alt="SwiftUI Preview showing the card image skewed when resized." src="https://cdn-images-1.medium.com/max/370/1*Q0iBP6rimCCTlfL3P57eDg.png" /></figure><p>Luckily, this is a common issue when resizing images inside a container with a specified size that we have already solved. :) We’re going to apply our .fitToAspectRatio() modifier so that our Card is more adaptable to its container. For details about the .fitToAspectRatio() modifier and resizing images in SwiftUI, check out<a href="https://medium.com/expedia-group-tech/resizing-images-in-swiftui-e65ced420b81"> Resizing Images in SwiftUI</a><em>.</em></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0d16b649b27c4fa3c9b45ceba4548ec1/href">https://medium.com/media/0d16b649b27c4fa3c9b45ceba4548ec1/href</a></iframe><p>Our images are now the same size, but our cards are not all aligned. Our SwiftUI Card can vary in height due to the title .lineLimit() being able to span up to 2 lines.</p><figure><img alt="Screenshot of collection view showing the SwiftUI cards not all aligned to the top." src="https://cdn-images-1.medium.com/max/439/1*oGXAlNuv67V4ZL66wUcpFA.png" /></figure><p>By default, SwiftUI Views are centered vertically to their container, which is what is happening here. To ensure our content gets aligned to the top of our UIViewCollectionCell, we need to add a Spacer().</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e67218cbcb3114c078a53782e64eb65e/href">https://medium.com/media/e67218cbcb3114c078a53782e64eb65e/href</a></iframe><p>Nice. :) This is looking good now.</p><figure><img alt="Screenshot of collection view showing the SwiftUI cards are now all aligned to the top." src="https://cdn-images-1.medium.com/max/439/1*XF2ENKowHdKnkgTDOKtdog.png" /></figure><h3>Caveats</h3><p>So far so good, right? Let’s add a button to our SwiftUI View.</p><figure><img alt="Screenshot of the SwiftUI property card that has a heart button added to the top right corner of the card over the property image." src="https://cdn-images-1.medium.com/max/268/1*I8HU23onWd4dnyvZdl_qpA.png" /></figure><h4>Do not use @State</h4><p>Remember that UICollectionViewCell gets reused for performance? For this reason, we need to make sure that our embedded SwiftUI Views do not hold any @State, as it could get wiped out as soon as the cell is no longer visible.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ed48ee6fae3ce9f8c3829c7121035764/href">https://medium.com/media/ed48ee6fae3ce9f8c3829c7121035764/href</a></iframe><p>Here, you&#39;ll notice we lose the heart state of the first cell.</p><figure><img alt="Animated gif showing the user scrolling through the collection view and “hearting” several properties. When the user scrolls back through the collection view, the hearts are no longer “hearted”." src="https://cdn-images-1.medium.com/max/438/1*UQBkaWHFNL3K3S2Hw-kuEQ.gif" /></figure><p>If your cell needs state, move this logic up higher in the View hierarchy by leveraging @EnvironmentObject.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/45d7d87bfa023ae401b5d2bd5fc55a92/href">https://medium.com/media/45d7d87bfa023ae401b5d2bd5fc55a92/href</a></iframe><p>Using @EnvironmentObject will ensure your cell gets its state every time it&#39;s reinitialized.</p><figure><img alt="Animated gif showing the user scrolling through the collection view and “hearting” several properties. Now, when the user scrolls back through the collection view, the hearts are still “hearted”, which is what we would expect." src="https://cdn-images-1.medium.com/max/438/1*QlUqmLkeXH9IDw4EGe5Vsw.gif" /></figure><h4>Beware UICollectionViewDelegate and SwiftUI Buttons</h4><p>When we are showing a property card in the Vrbo iOS app, we typically present that property’s details once you tap on the card.</p><figure><img alt="Screenshot of property details modal that shows the property image, description, availability and other information about the property." src="https://cdn-images-1.medium.com/max/439/1*nBgPlz9ndwCRa_Tu8sH9ew.png" /></figure><p>In our example, we would leverage the UICollectionViewDelegate to achieve this.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f0b19da84117b45d20a76328d58952e9/href">https://medium.com/media/f0b19da84117b45d20a76328d58952e9/href</a></iframe><p>Let’s see what happens with this hierarchy in conjunction with the didSelectItemAt method of the UICollectionViewDelegate:</p><pre>// View Hierarchy</pre><pre>UICollectionViewCell<br>  -&gt; SwiftUI View - (Property Card)<br>     -&gt; SwiftUI Button - (Heart Button)</pre><figure><img alt="Animated gif showing that when the heart button is tapped, the property details modal is presented. This is not the expected behavior. The property details modal should only be presented when the card is tapped, not when the card’s heart button is tapped." src="https://cdn-images-1.medium.com/max/438/1*96pOsZ6Iz7ZEj31-1d1BwQ.gif" /></figure><p>Not what we want at all! We expect the heart button to be toggled and not to be taken to the property details view, but instead we get both since the didSelectItemAt delegate method is also getting called.</p><p>This is what happens when we use a UIButton instead in conjunction with the didSelectItemAt method of the UICollectionViewDelegate.</p><pre>// View Hierarchy</pre><pre>UICollectionViewCell<br>  -&gt; SwiftUI View - (Property Card)<br>  -&gt; UIButton - (Heart Button)</pre><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8af1dbf74b9fcc14a410a1aed1141b55/href">https://medium.com/media/8af1dbf74b9fcc14a410a1aed1141b55/href</a></iframe><figure><img alt="Animated gif showing that when the heart button it is tapped, the property details modal is not presented." src="https://cdn-images-1.medium.com/max/466/1*KXfh497lnZl38739dp4bxQ.gif" /></figure><p>This is the behavior we want. The heart should be toggled when tapped and you should only be taken to the property details when the card is tapped.</p><h4>What are our options for handling interactivity in a SwiftUI View embedded in a UICollectionViewCell?</h4><ul><li>Don’t hook into the <strong>didSelectItemAt</strong> UICollectionViewDelegate method if you&#39;re integrating a wrapped SwiftUI View. You could implement your own <strong>didSelectItemAt</strong> delegate method. SwiftUICollectionViewCell could even implement such a delegate out of the box if needed.</li><li>Don’t bother with an embedded SwiftUI View if your UICollectionViewCell content has a lot of interactive elements <strong>AND</strong> you also need to hook into the <strong>didSelectItemAt</strong> UICollectionViewDelegate method.</li><li>Define a UIButton inside of UICollectionViewCell and position it above your SwiftUI View, as we did above.</li><li>Just wait until your app deprecates support for iOS 13 and then replace your UICollectionView with a 100% SwiftUI LazyStack solution.</li></ul><h3>Conclusion</h3><p>While it’s technically possible to use SwiftUI inside of a UICollectionViewCell, whether you choose to do so will likely depend on your use case and whether it makes sense given the caveats. These are some questions you might ask yourself before attempting a SwiftUI/UICollectionView integration:</p><ul><li>Is my app close to deprecating iOS 13? If so, maybe you can wait until that happens and implement a SwiftUI LazyStack solution.</li><li>Does my cell content have interactive elements? If so, are you okay with implementing some workarounds if you also need to leverage UICollectionViewDelegate.</li></ul><p>Thanks for reading!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=aba7cbaf6d16" width="1" height="1" alt=""><hr><p><a href="https://medium.com/expedia-group-tech/swiftui-with-uicollectionview-aba7cbaf6d16">SwiftUI with UICollectionView</a> was originally published in <a href="https://medium.com/expedia-group-tech">Expedia Group Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Resizing Images in SwiftUI]]></title>
            <link>https://medium.com/expedia-group-tech/resizing-images-in-swiftui-e65ced420b81?source=rss-4af315f40d50------2</link>
            <guid isPermaLink="false">https://medium.com/p/e65ced420b81</guid>
            <category><![CDATA[ui]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[technology]]></category>
            <dc:creator><![CDATA[Kari Grooms]]></dc:creator>
            <pubDate>Thu, 15 Apr 2021 13:03:41 GMT</pubDate>
            <atom:updated>2021-04-15T13:03:41.208Z</atom:updated>
            <content:encoded><![CDATA[<h4><a href="https://medium.com/expedia-group-tech/software/home">EXPEDIA GROUP TECHNOLOGY — SOFTWARE</a></h4><h4>How to resize images to any aspect ratio without distortion</h4><figure><img alt="The same image is displayed four times, each at a different size. Images are positioned above the SwiftUI code that handles resizing the images." src="https://cdn-images-1.medium.com/max/1024/1*KPg4JF56OfySUbaXYUYWOw.png" /><figcaption>Image by author</figcaption></figure><p>In the Vrbo™️ (part of Expedia Group™) iOS app, we’ve recently converted some of our existing UIKit views over to SwiftUI and encountered issues when resizing images. The problem arises when we need to resize an image to a certain size and maintain the aspect ratio without skewing.</p><p>We initially encountered this while working on Vrbo’s iOS 14 Widget, when we needed the image to be a perfect square, and again when building a SwiftUI version of one of our property cards. We display property images on nearly every screen in the app and at varying sizes depending on the context, meaning it was important to solve this problem in a scalable way.</p><figure><img alt="Decorative separator" src="https://cdn-images-1.medium.com/proxy/1*YFelTg0GTTuYvgFyeJS18A.png" /></figure><h4>iOS Widget</h4><figure><img alt="Screenshot of Vrbo’s iOS 14 Widget in light mode, implemented with SwiftUI" src="https://cdn-images-1.medium.com/max/338/1*mRssZuBlrA4nTBXGtcjT1Q.png" /><figcaption>Vrbo’s iOS 14 Widget in light mode</figcaption></figure><figure><img alt="Screenshot of Vrbo’s iOS 14 Widget in dark mode, implemented with SwiftUI" src="https://cdn-images-1.medium.com/max/338/1*ELDhBUN5AZQJW0mI-GSkFQ.png" /><figcaption>Vrbo’s iOS 14 Widget in dark mode</figcaption></figure><h4>Property Card</h4><figure><img alt="Screenshot of Vrbo’s iOS app main screen featuring recently viewed property cards implemented with SwiftUI" src="https://cdn-images-1.medium.com/max/480/1*T4QMmYf1tVfmDNgwxlvxGg.png" /><figcaption>Vrbo’s iOS app main screen featuring property cards</figcaption></figure><figure><img alt="Decorative separator" src="https://cdn-images-1.medium.com/max/1024/0*b-pHEkkaYqyJdwSh.png" /></figure><h3>The Road to Resizing</h3><p>Starting from scratch, this is what you get when you render an <a href="https://developer.apple.com/documentation/swiftui/image">Image</a> in SwiftUI without any modifiers.</p><pre>// original size: 338 x 360<br>Image(&quot;bungalows&quot;)</pre><figure><img alt="SwiftUI Preview of image with no view modifiers applied. The image is displayed at its original size." src="https://cdn-images-1.medium.com/max/394/1*4c4ICThprxCq-Ue0X9lfxw.png" /><figcaption>SwiftUI Preview</figcaption></figure><p>Now things are going to start getting weird. First, we explicitly set the size via .frame().</p><pre>// try to override size<br>Image(&quot;bungalows&quot;)<br>    .frame(width: 100, height: 100)</pre><figure><img alt="SwiftUI Preview of image with a .frame() modifier applied. The image was not resized. It is still displayed at its original size; however, there is a blue rectangle indicating where the frame is, even though the image was not changed." src="https://cdn-images-1.medium.com/max/365/1*fCuNidGS-AqbOwXV67F6lg.png" /><figcaption>SwiftUI Preview</figcaption></figure><p><em>Note: the blue square is there to indicate where the frame is. This wouldn’t be visible in the UI.</em></p><p>Since nothing happens when you set the .frame() explicitly, let’s see what applying the <a href="https://developer.apple.com/documentation/swiftui/view/clipped(antialiased:)">.clipped()</a> modifier gets us.</p><pre>// this sorta works<br>Image(&quot;bungalows&quot;)<br>    .frame(width: 100, height: 100)<br>    .clipped()</pre><figure><img alt="SwiftUI Preview of image with .frame() and .clipped() modifiers applied. The image is not resized, but was cropped to the frame size." src="https://cdn-images-1.medium.com/max/339/1*9VvGb6lwhJFbqrwqZ2uOvA.png" /><figcaption>SwiftUI Preview</figcaption></figure><p>Better….but our image just got cropped and was not resized. SwiftUI has a <a href="https://developer.apple.com/documentation/swiftui/image/resizable(capinsets:resizingmode:)">.resizable() </a>view modifier, so what happens when we use that instead?</p><pre>Image(&quot;bungalows&quot;)<br>    .resizable()</pre><figure><img alt="SwiftUI Preview of the image using the .resizable() modifier. The image was resized to the size of its container; in this case, the entire height and width of the iPhone, resulting in the image being skewed and not retaining its aspect ratio." src="https://cdn-images-1.medium.com/max/469/1*uwoYx1gdqLWd6K_7apWk3w.png" /><figcaption>SwiftUI Preview</figcaption></figure><p>Not what we want, but at least the image is resized now. By default, the resizingMode is .stretch, which is what we see above, but you could also set this to .tile.</p><pre>// .stretch is default<br>Image(&quot;bungalows&quot;)<br>    .resizable(resizingMode: .tile)</pre><figure><img alt="SwiftUI Preview of the image using the .resizable() modifier with .tile as its resizeMode. The image was still resized to the size of its container, but this time the image is tiled across the container instead of being stretched to fit, maintaining its aspect ratio." src="https://cdn-images-1.medium.com/max/460/1*JKtRfpwJzYnR4dXxoi32Dg.png" /><figcaption>SwiftUI Preview</figcaption></figure><p>The image looks better, but this is still not what we want. Let’s try explicitly setting the .frame() again, but this time in conjunction with the .resizable() modifier.</p><pre>// specify an exact size<br>Image(&quot;bungalows&quot;)<br>    .resizable()<br>    .frame(width: 338, height: 200)</pre><figure><img alt="SwiftUI Preview of the image using both the .resizable() and .frame() modifiers. The image is resized, but it is skewed and has lost its aspect ratio." src="https://cdn-images-1.medium.com/max/391/1*uXMPgxqNHBY8mbuHHoLyHQ.png" /><figcaption>SwiftUI Preview</figcaption></figure><p>It’s skewed! That’s not going to work. Let’s try to resize by aspect ratio.</p><pre>// use aspect ratio + .fit to resize<br>Image(&quot;bungalows&quot;)<br>    .resizable()<br>    .aspectRatio(4/3, contentMode: .fit)</pre><figure><img alt="SwiftUI Preview with .resizable() and .aspectRatio() modifiers applied. The image is resized as expected, but the image is still skewed and did not maintain its aspect ratio." src="https://cdn-images-1.medium.com/max/412/1*bKiQLiwKi0WkHPQ6cPukPQ.png" /><figcaption>SwiftUI Preview</figcaption></figure><p>Using .fit still gets us a skewed image. Instead, let&#39;s try using .fill</p><pre>// use aspect ratio + .fill to resize<br>Image(&quot;bungalows&quot;)<br>    .resizable()<br>    .aspectRatio(4/3, contentMode: .fill)</pre><figure><img alt="SwiftUI Preview of the image using .fill for the .aspectRatio() modifier. The image is resized, but this time it fills the container and is still skewed, not maintaining its aspect ratio." src="https://cdn-images-1.medium.com/max/469/1*pf_WX5JihVlit028UIj2qg.png" /><figcaption>SwiftUI Preview</figcaption></figure><p>Well that went wrong. We’re going to need to try something different…or Rectangle.</p><pre>// let the trickery begin<br>ZStack {<br>    Rectangle()<br>        .fill(Color(.gray))<br>	.aspectRatio(4/3, contentMode: .fit)<br>            <br>    Image(&quot;bungalows&quot;)<br>}</pre><figure><img alt="SwiftUI Preview of image stacked on top of a Rectangle. The image is displayed at its original size and the Rectangle is set to the desired size." src="https://cdn-images-1.medium.com/max/412/1*DOXvzbCGHFpRYTek2uOtJw.png" /><figcaption>SwiftUI Preview</figcaption></figure><p><a href="https://developer.apple.com/documentation/swiftui/zstack">ZStack</a> is how we stack things on top of each other in SwiftUI. The Rectangle is exactly the size we want our image to be. Can we make our image the same size as the Rectangle?</p><pre>// getting closer...<br>ZStack {<br>    Rectangle()<br>        .fill(Color(.gray))<br>	.aspectRatio(4/3, contentMode: .fit)<br>            <br>	Image(&quot;bungalows&quot;)<br>	    .resizable()<br>	    .layoutPriority(-1) // special sauce<br>}</pre><figure><img alt="SwiftUI Preview of the image resized to the same size as the Rectangle(). The image, however, is distorted again." src="https://cdn-images-1.medium.com/max/413/1*IFI-IAOpq9h4ha_JfZQalg.png" /><figcaption>SwiftUI Preview</figcaption></figure><p>We can! We’re giving <a href="https://developer.apple.com/documentation/swiftui/view/layoutpriority(_:)">.layoutPriority()</a> to the Rectangle so that it is dictating the size instead of the Image. But now our image is skewed again! We need to fix that.</p><pre>// Come on!!!<br>ZStack {<br>    Rectangle()<br>        .fill(Color(.gray))<br>	.aspectRatio(4/3, contentMode: .fit)<br>            <br>    Image(&quot;bungalows&quot;)<br>	.resizable()<br>	.aspectRatio(contentMode: .fill) // fix skewed image<br>	.layoutPriority(-1)<br>}</pre><figure><img alt="SwiftUI Preview of the image using .fill. The image is no longer skewed, but now it is no longer the same size as our Rectangle. There is a blue outlined rectangle indicating where the Rectangle() is." src="https://cdn-images-1.medium.com/max/413/1*LkvXAvVCegalbsUpsw1GFA.png" /><figcaption>SwiftUI Preview</figcaption></figure><p><em>Note: the blue square is there to indicate where the Rectangle is. This wouldn’t be visible in the UI.</em></p><p>One last modifier and we’ve got this! We’re bringing <a href="https://developer.apple.com/documentation/swiftui/view/clipped(antialiased:)">.clipped()</a> back!</p><pre>// Yes!<br>ZStack {<br>    Rectangle()<br>	.fill(Color(.gray))<br>	.aspectRatio(4/3, contentMode: .fit)<br>            <br>    Image(&quot;bungalows&quot;)<br>	.resizable()<br>	.aspectRatio(contentMode: .fill)<br>	.layoutPriority(-1)<br>}<br>.clipped() // magic</pre><figure><img alt="SwiftUI Preview of the image resized correctly and maintaining its aspect ratio." src="https://cdn-images-1.medium.com/max/413/1*NppOWQVdcTi33UYHHE9G0w.png" /><figcaption>SwiftUI Preview</figcaption></figure><figure><img alt="Decorative separator" src="https://cdn-images-1.medium.com/max/1024/0*f8i0FoxD9FY6kyLA.png" /></figure><h3>The Solution</h3><p>Now that we finally have a working solution, we need to make this reusable so that we never have to go through this again.</p><p>Let’s convert this to a <a href="https://developer.apple.com/documentation/swiftui/viewmodifier">ViewModifier</a> so that we can apply it to any Image.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ad13d43700070e301757a31b9cefe6f1/href">https://medium.com/media/ad13d43700070e301757a31b9cefe6f1/href</a></iframe><p>A few things to note about the final solution:</p><ul><li>We added an AspectRatio enum for the most common usages in the Vrbo iOS app. This solution supports both our predefined aspect ratios as well as a custom aspect ratio.</li><li>.resizable() was moved to the Image extension. .resizable() can only be applied to an Image, so it cannot be applied inside of the ViewModifier, which expects <a href="https://developer.apple.com/documentation/swiftui/viewmodifier/content">Content</a> that could be any type of View.</li><li>.aspectRatio(contentMode: .fill) was replaced with its shortcut modifier, <a href="https://developer.apple.com/documentation/swiftui/view/scaledtofill()">.scaleToFill()</a></li><li>We set the Rectangle fill color to be .clear instead of .gray. The only time the Rectangle would ever show would be if the image failed to render. For the Vrbo iOS app, we typically combine this modifier with other modifiers for providing a fallback state. However, this modifier could be expanded to support a fallback image or a fallback color.</li></ul><figure><img alt="Decorative separator" src="https://cdn-images-1.medium.com/max/1024/0*-UZ1UM1BSg11FwyP.png" /></figure><h3>Usage</h3><pre>Image(&quot;resorts&quot;)<br>    .fitToAspectRatio(.square)</pre><figure><img alt="SwiftUI Preview of image resized to a square. Image is of an outdoor pool at a luxury resort with white umbrellas, white beach lounge chairs and palm trees." src="https://cdn-images-1.medium.com/max/330/1*D_pyFKPi25vi8OY51hFtCQ.png" /><figcaption>SwiftUI Preview of image resized to a square</figcaption></figure><pre>Image(&quot;resorts&quot;)<br>    .fitToAspectRatio(.fourToThree)</pre><figure><img alt="SwiftUI Preview of image resized to 4:3 aspect ratio. Image is of an outdoor pool at a luxury resort with white umbrellas, white beach lounge chairs and palm trees." src="https://cdn-images-1.medium.com/max/329/1*YdI472Pv1IxIh5rxQ8ZxCA.png" /><figcaption>SwiftUI Preview of image resized to 4:3 aspect ratio</figcaption></figure><pre>Image(&quot;resorts&quot;)<br>    .fitToAspectRatio(.threeToFour)</pre><figure><img alt="SwiftUI Preview of image resized to 3:4 aspect ratio. Image is of an outdoor pool at a luxury resort with white umbrellas, white beach lounge chairs and palm trees." src="https://cdn-images-1.medium.com/max/330/1*d48LqACFOoxo8zX6sodkWw.png" /><figcaption>SwiftUI Preview of image resized to 3:4 aspect ratio</figcaption></figure><pre>Image(&quot;resorts&quot;)<br>    .fitToAspectRatio(1.5)<br>    .clipShape(RoundedRectangle(cornerRadius: 8))</pre><figure><img alt="SwiftUI Preview of image with rounded corners and resized to a custom 3:2 aspect ratio. Image is of an outdoor pool at a luxury resort with white umbrellas, white beach lounge chairs and palm trees." src="https://cdn-images-1.medium.com/max/327/1*TmsGDcZZ0wtHYQgSZJpqUg.png" /><figcaption>SwiftUI Preview of image with rounded corners and resized to a custom 3:2 aspect ratio</figcaption></figure><p>Now, no matter the size of a container View, our image will always render to our desired aspect ratio without becoming distorted. Thanks for reading!</p><p><a href="https://lifeatexpediagroup.com/?utm_source=techblog&amp;utm_medium=career&amp;utm_campaign=postfooter">Learn more about technology at Expedia Group</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e65ced420b81" width="1" height="1" alt=""><hr><p><a href="https://medium.com/expedia-group-tech/resizing-images-in-swiftui-e65ced420b81">Resizing Images in SwiftUI</a> was originally published in <a href="https://medium.com/expedia-group-tech">Expedia Group Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Lessons in SwiftUI]]></title>
            <link>https://medium.com/expedia-group-tech/lessons-in-swiftui-2a1e1ae03f2a?source=rss-4af315f40d50------2</link>
            <guid isPermaLink="false">https://medium.com/p/2a1e1ae03f2a</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[ui]]></category>
            <dc:creator><![CDATA[Kari Grooms]]></dc:creator>
            <pubDate>Thu, 08 Apr 2021 13:01:16 GMT</pubDate>
            <atom:updated>2021-04-08T15:31:25.270Z</atom:updated>
            <content:encoded><![CDATA[<h4><a href="https://medium.com/expedia-group-tech/software/home">EXPEDIA GROUP TECHNOLOGY — SOFTWARE</a></h4><h4>Getting started with SwiftUI in Vrbo iOS apps</h4><figure><img alt="Screenshot of SwiftUI code and UIKit code side-by-side implementing the same UI" src="https://cdn-images-1.medium.com/max/1024/1*T0gxcpub7qI-GZpDorztuQ.png" /><figcaption>Image by author</figcaption></figure><p>With the second major release of <a href="https://developer.apple.com/xcode/swiftui/">SwiftUI</a>®, we can now use it to build an entire app for multiple Apple® platforms. After the announcements at WWDC 2020, it was clear that we needed to bring SwiftUI capabilities to the Vrbo™️ (part of Expedia Group™) iOS apps as soon as possible. Here are some of the lessons we’ve learned.</p><h3>SwiftUI == Less Code</h3><p>The code speaks for itself; here’s an apples to apples comparison of how to implement the simple view below in SwiftUI versus UIKit.</p><figure><img alt="A mostly blank view with a label and button in the center" src="https://cdn-images-1.medium.com/max/466/1*yooz8cYQihBmzdAebkmJqQ.png" /></figure><h4>SwiftUI Implementation</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b32d3ce4a967334baf464b71931e325e/href">https://medium.com/media/b32d3ce4a967334baf464b71931e325e/href</a></iframe><h4>UIKit Implementation</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/07870daa46db89ed6b0f3d9e07f0957a/href">https://medium.com/media/07870daa46db89ed6b0f3d9e07f0957a/href</a></iframe><p>The code above leverages SwiftUI view modifiers added to Vrbo’s internal UI library. The UIKit equivalents to these SwiftUI view modifiers are many times larger in terms of lines of code.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3388a1fb2a1a3abcbdc828bee215da54/href">https://medium.com/media/3388a1fb2a1a3abcbdc828bee215da54/href</a></iframe><h3>SwiftUI in iOS 13 != SwiftUI in iOS 14</h3><p>Just because some functionality or component exists in UIKit, does not mean that there will be an equivalent in SwiftUI. UIPageViewController, UIProgressView and others did not have a SwiftUI equivalent until iOS 14 and there are a few that still don’t (UISearchController, UIImagePickerController, etc.)<a href="#7c79">¹</a>. A complete list of UIKit components and their SwiftUI counterparts can be found <a href="https://fuckingswiftui.com/#uikit-equivalent-in-swiftui">here</a>.</p><p>Until our app drops support for iOS 13, we likely won’t be able to leverage all of the new SwiftUI capabilities highlighted at WWDC 2020.</p><h4>Lazy Stacks</h4><p>Specifically, we need to keep our <em>UICollectionViews</em> around while we support iOS 13. Their replacements, <a href="https://developer.apple.com/documentation/swiftui/lazyvstack"><em>LazyVStack</em></a> and <a href="https://developer.apple.com/documentation/swiftui/lazyhstack"><em>LazyHStack</em></a>, give us the same benefit of a <em>UICollectionView</em> by only creating children when needed for performance. Until then, we can likely use SwiftUI Views inside of the collection view cells, but not for the collection view itself.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/dd836b8657159a0f076e3d00a0b1e8ed/href">https://medium.com/media/dd836b8657159a0f076e3d00a0b1e8ed/href</a></iframe><h3>SwiftUI does not replace UIKit</h3><p>At first I thought SwiftUI was a replacement for UIKit, but it is not. SwiftUI leverages UIKit. It is not an API written completely on top of UIKit though. For example, <em>VStacks</em> and <em>HStacks</em> do not render <em>UIStackViews</em> under the hood<a href="#7c79">²</a>, but in some cases portions of UIKit are used. <br>For example, <em>SegmentedPickerStyle</em> leverages <em>UISegmentedControl</em> from UIKit and there are likely other examples where SwiftUI is dependent on some part of UIKit.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/eca93032d9a901c7cb545dbdf7f54880/href">https://medium.com/media/eca93032d9a901c7cb545dbdf7f54880/href</a></iframe><h3>Composition order of view modifiers matters</h3><p>In UIKit, we use class properties to apply styles. We use <em>ViewModifiers </em>to apply styles in SwiftUI and applying them in a different order may have different results.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/430139d7153b9989f9d48764e4b585a7/href">https://medium.com/media/430139d7153b9989f9d48764e4b585a7/href</a></iframe><figure><img alt="Preview of button where the background color is only applied to the text and not the full size of the button. There is no visible corner radius being applied to the button." src="https://cdn-images-1.medium.com/max/460/1*ghtBcdmRzXiVCKQntejngQ.png" /><figcaption>SwiftUI Preview</figcaption></figure><p>In the example above, we applied the background color modifier before our size modifiers. We need to flip the order of these to ensure our background color fills the entire size of the button.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4fbf0e2cd641f17738d56f1b100bb062/href">https://medium.com/media/4fbf0e2cd641f17738d56f1b100bb062/href</a></iframe><figure><img alt="Preview of button where the background color is applied correctly. The background extends to the full size of the button and there is a visible corner radius." src="https://cdn-images-1.medium.com/max/438/1*uxHwszN6w974cENXW6yjAg.png" /><figcaption>SwiftUI Preview</figcaption></figure><p>Order matters when it comes to duplicate view modifiers as well. With view modifiers, the first application of a modifier takes priority over any subsequent applications.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/862c713c3de5a3c0a74cfd7b8b76a13c/href">https://medium.com/media/862c713c3de5a3c0a74cfd7b8b76a13c/href</a></iframe><figure><img alt="Text is blue instead of black." src="https://cdn-images-1.medium.com/max/450/1*clu-9jzbI3e5bttJ1_URAA.png" /><figcaption>SwiftUI Preview</figcaption></figure><h3>View modifiers can be applied to several Views at once</h3><p>I could definitely see a use case where we apply some sort of<em>.textBase()</em> modifier at a very top level View…maybe even an entire module or entire app.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2929723f2d3971832623e1dfe74563a9/href">https://medium.com/media/2929723f2d3971832623e1dfe74563a9/href</a></iframe><figure><img alt="All of the text is styled with the same .body font style, except for the third line, where the font has been overridden with the .headline font style." src="https://cdn-images-1.medium.com/max/632/1*vNTSVTdThY1-RQ69Ty_Bgg.png" /><figcaption>SwiftUI Preview</figcaption></figure><h3>Keep logic separate from SwiftUI Views for unit testing</h3><p>At the time of writing, there doesn’t seem to be an easy way to inspect views in unit tests without digging into internals (directly or via an external library). We can work around some of this though. By keeping the View logic in its own object, we can unit test that object instead of the View. Let’s look at this in practice with buttons.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/498/1*gHSIiiKB0Ix6NVFSk9q-Nw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/416/1*fzX6thp8rqk9Rk91omgeKw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/488/1*4eT1WYK70ePMr84T6jsIZA.png" /><figcaption>Variations of buttons in the Vrbo app: icon only, default, primary, overlay, link, inverse and buttons in dark mode</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/652/1*XKEfuA9nm29yTVF_g5V6BQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*6AHrAPGXdMSLoVbCYwDpKA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/400/1*KDMZcDcHrepQp2oV-1pl4Q.png" /><figcaption>Variations of buttons in the Vrbo app: small, extra small, medium, large, multi-line and disabled</figcaption></figure><p>We primarily use four styles of buttons in the Vrbo iOS app. Each of these button styles supports different sizes, each style has an inverse version of itself, buttons can have icons or not and we support dark mode too, so we have quite a few variations.</p><p>In SwiftUI, the method for styling buttons is the <a href="https://developer.apple.com/documentation/swiftui/view/buttonstyle(_:)-7qx1"><em>.buttonStyle()</em></a> view modifier, which accepts a <a href="https://developer.apple.com/documentation/swiftui/buttonstyle"><em>ButtonStyle</em></a>. Internally, we have a single ButtonStyle that handles all of our variations and we’ve moved all of the view logic for these to a <em>ButtonViewModel</em>.</p><pre>struct ButtonViewModel {</pre><pre>    // MARK: Config</pre><pre>    let iconMode: ButtonIconMode? // .iconOnly<br>    let inverse: Bool<br>    let size: ButtonSize // .large, .medium, .small, .extraSmall<br>    let type: ButtonType // .primary, .secondary, .link, .overlay<br>    <br>    // ...<br>}</pre><p>Our ButtonViewModel derives most of its values for the View from the above configuration models as demonstrated in the code snippet below.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5c9830ab972b6b74dd2e26b382099a0d/href">https://medium.com/media/5c9830ab972b6b74dd2e26b382099a0d/href</a></iframe><p>Buttons can also have the following states:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0531f44b9dc4bb45dd8a833c62671334/href">https://medium.com/media/0531f44b9dc4bb45dd8a833c62671334/href</a></iframe><p>The <em>isEnabled</em> property lets us know whether the button is disabled and the <em>isPressed</em> property lets us know if the button is being pressed. We reflect these states in our UI based off of these properties as well as the configuration models.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a222195b85901008fdc14bdde09b6b47/href">https://medium.com/media/a222195b85901008fdc14bdde09b6b47/href</a></iframe><p>Here’s how we use this ViewModel in our <em>ButtonStyle</em>.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7325a51648da85f611c74373df0ccec7/href">https://medium.com/media/7325a51648da85f611c74373df0ccec7/href</a></iframe><p>Now, we can unit test the ViewModel without needing to instantiate a SwiftUI View. Here are some examples of unit tests.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/eff0d6a89a08bd994cff36277505528a/href">https://medium.com/media/eff0d6a89a08bd994cff36277505528a/href</a></iframe><h3>What about SwiftUI and UIKit together?</h3><p>Like a lot of other apps, Vrbo is and will be in a hybrid state of SwiftUI and UIKit for a little while. How that looks is still taking shape, but Apple provides mechanisms for interoperability.</p><h4>SwiftUI inside of UIKit</h4><p>You can wrap SwiftUI Views inside UIViewControllers via <a href="https://developer.apple.com/documentation/swiftui/uihostingcontroller">UIHostingController</a>, which is how we can start using SwiftUI immediately in our existing apps. It is a definite path forward.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/822e2deb92624411171bc2df6db4fc09/href">https://medium.com/media/822e2deb92624411171bc2df6db4fc09/href</a></iframe><h4>UIKit inside of SwiftUI</h4><p>You can also wrap UIViews and UIViewControllers inside of SwiftUI Views via <a href="https://developer.apple.com/documentation/swiftui/uiviewrepresentable">UIViewRepresentable</a> and <a href="https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable">UIViewControllerRepresentable</a>. When I was experimenting with SwiftUI initially, I rewrote several portions of the Trip Boards UI while still leveraging existing UIViews in a few places.</p><figure><img alt="A Trip Board Card where the majority of the View is implemented with SwiftUI, and a small section of the card; a cluster of user avatars at the bottom of the card, is implemented with UIKit" src="https://cdn-images-1.medium.com/max/423/1*3UWjCSZTUeUMViHh_iORHg.png" /><figcaption>Trip Board Card</figcaption></figure><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3c20095dc0ede8f4538ce4f667c7138c/href">https://medium.com/media/3c20095dc0ede8f4538ce4f667c7138c/href</a></iframe><p>The example above was done for proof of concept purposes, but as we move forward with SwiftUI it will be interesting to see what use cases there are for wrapping like this. My initial thought was that it might make sense to work from the inside out, where you convert your innermost UIViews to SwiftUI views first, resulting in a more one-directional approach. When using a hybrid approach, it can quickly become like the movie Inception<a href="#7c79">³</a> where it’s a View / UIView / UIViewController within a View / UIView / UIViewController instead of a dream within a dream.</p><p>Since SwiftUI Views are more performant than UIViews<a href="#7c79">⁴</a>, it may also make sense to go ahead and convert existing code to SwiftUI versus wrapping existing UIKit code.</p><pre>// One-directional approach</pre><pre>- UIViewController<br>    - UIHostingViewController<br> 	- SwiftUI View<br> 	    - SwiftUI View<br> 	    - SwiftUI View</pre><pre>// Hybrid approach</pre><pre>- UIViewController<br>    - UIHostingViewController<br>        - SwiftUI View<br>            - SwiftUI View<br>            - SwiftUI View<br>        - UIViewControllerRepresentable<br>            - UIViewController<br>                - UIView<br> 	            - UIHostingViewController<br>    		        - SwiftUI View</pre><h3>SwiftUI Previews did not work</h3><p>I think one of the most exciting features of SwiftUI is Previews. It is like hot reloading for web development (it’s actually probably closer to live reloading vs hot reloading). Prior to this, Interface Builder was the mechanism for “previewing” views without building the app, but these were not truly a preview of how the UI would look. Previews also go a step further and allow you to have multiple instances for various states of your UI. So you could have a preview of your view for dark mode, light mode, iPhone or iPad, side-by-side while you’re developing!</p><figure><img alt="SwiftUI Preview of a property card" src="https://cdn-images-1.medium.com/max/814/1*lIA5GND-x49neGC8ozdOVw.png" /><figcaption>SwiftUI Preview</figcaption></figure><p>I was super stoked for these, but then when it was SwiftUI play time…they didn’t work.</p><h4>Local Environment</h4><p>There are a few of requirements for SwiftUI Previews:</p><ul><li>Xcode 11+</li><li>macOS Catalina</li><li>Have <em>iOS 13 or later</em> set as your Deployment Target for your Debug configuration</li></ul><p>I met all of these requirements for my local environment, but Previews still did not work in the Vrbo iOS app or for our internal UI library, UIToolkit. So what was up?</p><h4>Naming Collisions</h4><p>In both projects, there were naming collisions that were causing SwiftUI Previews to not work. You cannot have a struct/class/enum/protocol that is the same name as your project. These were the fixes:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4f65452b426f6d3b773ac1a62261962a/href">https://medium.com/media/4f65452b426f6d3b773ac1a62261962a/href</a></iframe><p>Another alternative would have been to rename the project instead of the object names.</p><h4>Project Configuration</h4><p>After implementing the above fixes, Previews were still not working in the Vrbo iOS app (No!!!). The reason for this may be less common, but the Vrbo iOS app is modularized, so the app is broken up into several frameworks. The app is also still using Carthage for some dependencies. There was an error that one of our dependencies could not be loaded. Previews were looking for these in the wrong place based on our Build Settings. We needed to update the <em>@rpath</em> to look for frameworks in the Carthage build directory for each of our frameworks.</p><figure><img alt="Screenshot of where to update the @rpath in Xcode Build Settings" src="https://cdn-images-1.medium.com/max/1024/1*9Wc4iinYayDQSpoWZ0-FQQ.png" /></figure><p>While there is a lot more to dive into with SwiftUI, I hope that these initial learnings have been helpful.</p><p>Thanks for reading! ☺</p><h4>Sources</h4><p>[1] <a href="https://fuckingswiftui.com/#uikit-equivalent-in-swiftui">https://fuckingswiftui.com/#uikit-equivalent-in-swiftui</a><br>[2] <a href="https://wwdcbysundell.com/2019/swiftui-relationship-to-uikit-appkit/">https://wwdcbysundell.com/2019/swiftui-relationship-to-uikit-appkit/</a><br>[3] <em>Inception movie</em> <a href="https://en.wikipedia.org/wiki/Inception">https://en.wikipedia.org/wiki/Inception</a><br>[4] <a href="https://www.hackingwithswift.com/books/ios-swiftui/why-does-swiftui-use-structs-for-views">https://www.hackingwithswift.com/books/ios-swiftui/why-does-swiftui-use-structs-for-views</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2a1e1ae03f2a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/expedia-group-tech/lessons-in-swiftui-2a1e1ae03f2a">Lessons in SwiftUI</a> was originally published in <a href="https://medium.com/expedia-group-tech">Expedia Group Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>