EXPEDIA GROUP TECHNOLOGY — SOFTWARE

6 Wild Things I Learned about SwiftUI

Gotchas writing iOS apps with SwiftUI

Martin Note
Expedia Group Technology

--

Two wild horses nuzzle in a rugged landscape
Photo by Claire Nolan on Unsplash

The release of iOS 14 harkened the arrival of App Widgets, the first time Apple® has allowed us to take up more prime real estate on a user’s home screen. Vrbo™️, part of Expedia Group™, released our Trip Countdown Widget to coincide with the release of the new OS. App Widgets can only be written in SwiftUI™️, the new declarative framework that Apple is pushing to be the future of iOS.

decorative separator

At Expedia Group™️, I’ve historically been a web engineer focused on building out the Vrbo team’s React-based design system. In 2020, I shifted gears to tackle our native platforms — bringing Android™️ and iOS up to the maturity that our web ecosystem has achieved. As part of this work, I made it a summer goal to become fluent in SwiftUI, a way of reluctantly re-entering iOS development.¹ The timing of this plus the incoming release of iOS 14 meant that most of my learning was hands-on integrating it within WidgetKit ² and the Traveler App — our first use of Swift UI in a production app!

I’ll share some of the random, wild things I discovered about the framework. This list is more a bunch of gotchas that I hope to save you from. If you are curious about trying out SwiftUI, I recommend Apple’s online tutorial.

1. Reverse the way you think about any sort of styling cascade.

I discovered this while attempting to change the color of one of our SwiftUI text view modifiers — they mostly all set our text to our brand’s neutral-darker color (in hex code: #292929). I was attempting to chain a .foregroundColor() on top of that to no avail. For an example to demonstrate what’s going on, take this small snippet. For those new to SwiftUI, Text is one of the primitive SwiftUI Views that allows rendering of text. It accepts your text as a param. From there you can chain on view modifiers — here we are adding padding and changing the text color:

What color would you expect the text to be? Red, right? Nope: It’s green, because that modifier was declared first. This is the exact opposite way I have trained my brain to think after years of CSS. A deeper dive into why modifier order counts. I’ve already seen a few other colleagues tripped up by this.

2. The promise of Live Previews is great, even if they do not always work in your app.

One of the true velocity enablers in SwiftUI is the ability to turn on a Live Preview of the app screen layout you are composing — no more waiting several minutes for a build to complete to verify if what you are doing is working correctly.³ For Web developers, this is akin to hot reloading of your test harness. Unfortunately, these don’t always work (sometimes due to bugs in XCode Betas, sometimes because our Traveler App was not yet fully configured for it).

SwiftUI Apps and Views are incredibly easy to set up. When I was mostly focused on layout/scaffolding of the App Widget, I easily spun up a Playground project, iterated over layout options, and when I found a successful combination, copied it right over into our Traveler App. What difficult layout problem required a lot of iteration? Ho ho ho, behold item 3:

3. Who knew making a square could be so difficult?

One of the main parts of the Trip Widget is a square shaped thumbnail of the rental property. The thumbnail is proportional to the height of the widget (minus padding).

Screenshot of an App Widget with square image on right side
Standard layout of our Trip Widget, with a proportional square image on the right hand side

The size of the widget varies across devices. Several sizing approaches were tested out, but nothing worked well.

  • Using .frame() to hard-set the height but that meant it would be smaller on larger sized devices
  • Using GeometryReader to calculate the widget’s height. GeometryReader allows you to do things like “Get the size of your parent container”, but it messes up the layout as it takes layout priority over everything else.

In SwiftUI, there is the concept of clipping to shape or using Shapes, but surprisingly there is no clear way to do what we were trying to accomplish: a square with proportional sizes based on its container.

Here’s what we finally figured out:

In this, we’re creating a ZStack (siblings are placed on top of one another). We start off with a Rectangle, setting a background color, using aspect ratio to make the sides the same height/width. Then putting in our property Image, making it resizeable, scaled to fill, and then stating that the layoutPriority of this is less important than the perfect aspect ratio, so don’t try any Landscape/Portrait funny business, SwiftUI. Finally, clip the space to use ContainerRelativeShape.

4. So much less syntactic sugar.

This goes without saying, and is the promise of SwiftUI. But for an engineer coming from a React background, it is very refreshing. One of the follow-ups after my Trip Widget work was to revisit our design system’s iOS demo app and see how easily the main component listing screen could be converted to a SwiftUI View. The original view utilized UITableViewController replete with all the requirements of that protocol, dequeuing of table cells, plus all the necessary property observers required for the child views. For visual reference (“Baseline” is the moniker for Vrbo’s design system):

Screenshot of an iOS app listing reusable layout components

Here is that view boiled down into SwiftUI:

List is a wrapper for UIKit’s UITableView, so it will handle all the memory allocation for you under the covers. Speaking as a web engineer who did not have to worry about memory management when dealing with long lists of data, this is a blessing! Also, compared to React, this is much easier to parse what is going on in the app screen.

5. Jumping from UIKit to SwiftUI and back again within an app.

There is quite a bit of interoperable capability to switch between the two methods of rendering UI within iOS. These boil down to three approaches:

  1. Rendering a SwiftUI View in a UIViewController. Use UIHostingController. Until we start shipping full SwiftUI Apps, this will likely be employed frequently.
  2. Navigating to a UIViewController from a SwiftUI View. Use UIViewControllerRepresentable. Our design system demo app has a simple one set up called IntegratedController. Currently the main demo list is a SwiftUI View and all child views are UIViewControllers that are navigated to from a NavigationLink SwiftUI component.
  3. Wrapping a UIKit component in SwiftUI. You can wrap UIKit views within UIViewRepresentable. However, for the majority of our UIKit custom components in UIToolkit, the complexity and difference in design patterns (MVC vs MVVM) likely makes the effort not worth it. Here’s some small work trying to make one of our iOS design system’s simplest custom components — a Chip — wrap within SwiftUI:

This renders this monstrosity:

Screenshot of an almost full screen grey shape with the words `Hello World!` in the middle
A very, very large pill

Constraints and height do not seem to carry over by default and the UIViewRepresentable seems to adopt SwiftUIs MO: Fill up all the space available.

6. Returning views from a function, without actually needing to use return.

A follow-up refactor of the Design System Demo App’s List view was to remove the UIViewControllers necessary to house our few existing SwiftUI demo views and just jump directly to the Views themselves. To accomplish this, the destination parameter of NavigationLink needed to return either a SwiftUI View or the previously mentioned IntegratedController. In the loosy-goosy world of JavaScript, you could simply return either type based on the demo type. Swift is strongly typed, so I immediately started getting errors about my getDestination method returning two different types. Rather than going down a rabbit hole attempting to build some sort of abstract factory pattern for this, I researched a bit approaches that are unique to SwiftUI, specifically “some View.” Views in SwiftUI are “cheap”, and our SwiftUI demos can be wrapped in the type-erased view AnyView, here’s the method:

Notice that the content is housed in a VStack, and the if/else statement is contained within that. Also notice: the complete lack of any `return` keyword. This was an “aha, of course!” moment for me when wrapping my head around how SwiftUI works.

Moving Forward: SwiftUI at Vrbo & Expedia Group

It is clear from the small amount we have dipped our toes into SwiftUI that there is a huge unlock here in terms of developer velocity — especially for React engineers who may want to try out an iOS rotation; the declarative nature of the framework has a familiar feel. Some of Vrbo’s new group collaboration features will be built in SwiftUI and we will continue to build out SwiftUI components within our iOS Design System framework.

decorative separator

[1]: My first foray into iOS development in 2019 was challenging as it was a very, very different developer experience compared to React. This is likely why I also chose to kick off my native design systems work by focusing on Android first!

[2]: This post won’t talk much about WidgetKit. I will certainly say that this is one of the few times I’ve ever worked on cutting edge APIs on a Beta release of an IDE. Frequently when asking questions to the more seasoned iOS Devs, there was some head scratching about “I dunno, you’re doing bleeding edge stuff here Marty.”

[3]: Yes, Storyboards already kind of do this, but we tend to create our views programmatically at Vrbo, thus the point is moot.

[4]: This sounds silly, but my colleague Kari and myself scoured all of the internet trying to find a solution for this, and we were only able to figure it out on our own.

Learn more about technology at Expedia Group

--

--

Martin Note
Expedia Group Technology

Multi-platform (Web, iOS, Android) UX Engineer living and working in Austin, TX. My speciality is helping build consistent, accessible design system tech.