SwiftUI in WELT: First Blood

Or how we started using SwiftUI in Production Code without (major) Headaches

César Vargas Casaseca
Axel Springer Tech
9 min readMay 6, 2021

--

With the Product Owner’s green light for dropping the support of iOS 12 downwards, a new world of juicy possibilities emerged in our development horizon. Together with iOS 13 two major new Apple frameworks were released: Combine and SwiftUI, the new framework to design and develop user interfaces declaratively and with less code. For the former we lost no time in replacing our aged third party library and start using Combine everywhere Reactive Programming or Promises were applied. For the latter, SwiftUI, even if we could barely contain our excitement to see it running our user interface, we were more cautious given the early mixed feedback from the iOS devs community.

In this story I want to present the process we followed to start using SwiftUI in our production codebase, from the planning strategy to the actual code writing. We will see why it might be good idea in the first place, how we can do it in a safe and interesting manner and what troubles we encountered and how we solved them. At the end you will find the conclusion with our personal opinion based on experience and tips for your first rendezvous with SwiftUI.

So, same as John Rambo in the first instalment of the franchise, it was time to start walking towards our sure fate and plan how we can integrate SwiftUI in our production environment. Notwithstanding, before starting writing code, a moment of pause and reflection is necessary: why do we even need to do that? Is it just the developer’s frenzy of new technologies, or will we really profit from it?

Photo by Braden Collum on Unsplash

Why?

Don’t panic, this is not going to be the zillionth post comparing SwiftUI and UIKit. But still, it is important to mention what was the drive when we decided to trigger the process.

For starters, as I said before, SwiftUI helps to create the same UI design as UIKit with significantly less code. The advantages that this produces are obvious: generally less code means better Readability, easier Maintainability, more Efficiency and substantially fewer expected Workload, that is, we will spend less working hours to achieve the same results, not only because of the amount of code required, but also because of the Live Preview used when development. So, with SwiftUI we expect to …

  • Improve Readability
  • Make the code easier to maintain
  • Enhance the code’s efficiency
  • Reduce the expected Workload

Not bad uh? I am convinced that this is just enough to sustain our decision to start using SwiftUI. But on top of that, with this new framework we will move even further in the direction of a more reactive and declarative syntax, a way that as remarked we started years ago and followed when introducing Combine. Because yeah, do you know what uses SwiftUI to implement its reactive logic? Bingo, Combine!

Bingo!

Apart from that, SwiftUI is allegedly easy to use, the code is clean and it can be integrated perfectly with UIKit. That means that we can conveniently migrate some parts of our UI code to SwiftUI, while the rest remains written within the bounds of the UIKit syntax.

Last but not least, we listen to our yearning to try something that might change we way we think and develop User Interfaces for many years to come. And we wanted to do that in real fire production code, the best arena to learn with the mistakes encountered along the way, both ours and those caused by such an early stage tool as SwiftUI.

Alrighty! We have now a why, what is missing to start working with it? Of course, a how!

How?

What a good question, how can we start writing our UI in SwiftUI? There are not many alternatives at this point, either we wait for new UI features to pop out from our backlog, or we migrate some of the already existing screens. The second option is less risky and more convenient to mitigate the learning curve pain of adapting this new technology. Basically, we just want to rewrite a simple yet custom screen. We want to find some challenges along the way, the key to learning something new.

Either we wait for new UI features to pop out from our backlog, or we migrate some of the already existing screens

The biggest caveat of this approach is the apparent lack of added value, or at least short term added value, outside of the devs team. After all, we are just replacing one screen … with the exact same screen. Thankfully, we have a negotiated time budget for technical improvements.

Given the clear benefits of refactoring the existing UIKit based Views to use SwiftUI (contained risks, smooth learning) and being able to endure the few drawbacks that might bring, we opted for that option. Thus, we then had to find an existing screen that matches our criteria, simple, but yet with some custom complexity.

The Stocks view is perfect for that. A table with custom designed cells that represents displaying a data model retrieved from a backend endpoint, together with a pull-to-refresh control that triggers the refresh of the screen content:

The Stocks screen, our Guinea Pig

Thanks to UIHostingController, it is painless to integrate this SwiftUI view back into the UIKit Navigation Controllers architecture.

Challenges

The development process of the basic features of this screen is very straightforward: the former UIKit UITableView is now replaced with a SwiftUI List, the cells are easily buildable with an HStack that composes the Texts. Those in turn, are rendered with different background colours that are applicable as view modifiers. It is here where SwiftUI supposedly excels and I can certify; what it used to be many lines of UIKit based code, it is now just a bunch of much more readable declarative code lines. In particular, the concept of having a single entry point for describing a view, is just awesome and much simpler to read and build. Plus one, SwiftUI!

What it used to be many lines of UIKit code, it now just a bunch of much more readable declarative code lines.

Granted that, It is when we bring our rebel side and try to go off road that we start suffering, especially if, still supporting iOS 13, are obliged to work with the first version of the framework.

For instance, at this time SwiftUI does not include a refresh control that mimics the action of the former and beloved UIKit’sUIRefreshControl, so we ourselves have to build it. Or better said, we can rely on this external library that achieves it by introspecting the hierarchy of the relevant UITableView that stands behind the SwiftUI scenes.

We might have to do some shady job to custom our design with SwiftUI

Introspect is a word that you will hear very often while working with SwiftUI: the first resort to be used in order to implement something not supported out of the box by SwiftUI will be to introspect the underlying elements behind the SwiftUI components, most likely UIKit ones, to modify them according to our requirements. SwiftUI-Introspect might be the first tool in your toolset when the time comes. The downside of this approach is obvious, apart from having to rely on an external dependency, with any new version of SwiftUI new changes in the implementation might come and break your flow. But still, until the iOS SDK includes all these components, there is not much we can do.

Likewise, ase we wanted to remove the extra separators that appear below the list by default, we had to modify the tableFooterView property of the table view appearance on appear, and reset it on disappear:

.onAppear {UITableView.appearance().tableFooterView = UIView()}.onDisappear {UITableView.appearance().tableFooterView = nil}

Another tweak to the UIKit underlying element to achieve our visual demands. Note that this happens only on iOS13, on iOS 14 by default there are no separators under the list.

Furthermore, the biggest challenge we face when working with SwiftUI is coming up with an architecture that binds our data to the view in a reactive way.

The true difficulty here resides not so much in implementing those bindings, but in shifting the mindset paradigm away from the UIKit-friendly architectures, such as MVC, VIPER or even MVVM. Now SwiftUI forces us to change our attitude into a new way of thinking, where of course the main premises of Clean Code stay, but declarativeness and reactiveness prevail. And while SwiftUI together with Combine provide the right tools for building a small sized codebase, for a larger, more complex, feature scalable and modular App it falls short.

For a larger, more complex, feature scalable and modular App SwiftUI falls short.

To illustrate without delving too deep, it is difficult to find a solution to persist our (complex) App State that is scalable, and at the same time easy to mutate along the different views. The given State property wrapper used by SwiftUI should be used with simple struct types (Int, String, Array …) and not be shared with other views.

The more modular, the cooler

In like fashion, it is not possible to split a large app state into smaller ones, for instance, to be used in different modules. With a more modular approach, each package would only have access to the state elements relevant to its functionality, making the architecture much more encapsulated and safe. As a consequence of these points, and also because of the lack of tools and documentation on the subject, it is very difficult to test a SwiftUI view or its side effects. This is very important; testability is a sine qua non condition for our business logic implementation.

Consequently, and thinking long term, we use The Composable Architecture to address these issues. TCA is a open source library that provides:

A few core tools that can be used to build applications of varying purpose and complexity. It provides compelling stories that you can follow to solve many problems you encounter day-to-day when building applications, such as, State management, Composition, Side effects and Testing

While TCA is optimal for SwiftUI, it can be also used together withUIKit, as well as on every other Apple platform (iOS, macOS, tvOS, and watchOS). If you want to know more about it plus more Swift functional programming topics, visit their Video/Blog site pointfree.co.

In that manner we solve the problems we enumerated before: we can now create our Stocks View in a separate, encapsulated and fully testable module, that fits perfectly into our modular App.

In short, when working with SwiftUI we faced these obstacles:

  • Lack of tools to display complex design elements (Refresh Control, List separators …)
  • No scalable and composable App State
  • Cumbersome Testability

For now we used Introspect and TCA to solve these issues, but we are confident that SwiftUI itself improves with future versions as it did with SwiftUI 2 on iOS 14.

The Aftermath

Photo by Johannes Plenio on Unsplash

In this story I just described how we introduced SwiftUI in our production code. We did it to learn and to have a more declarative, readable, efficient, smaller and easier to maintain code. Besides, I explained how migrating a simple screen would be the easiest entry point to start dealing with it. And furthermore, we know now what are the main challenges when venturing into this up-to-now uncharted territory, and what weapons do we have to emerge victorious.

I would like to thank my colleague Ivan Lisovyi for the huge contribution to this topic. As always, if you have any suggestion, correction or comment drop a message below.

Happy SwiftUIting!!

--

--

César Vargas Casaseca
Axel Springer Tech

Senior iOS Developer at Automattic. Allegedly a clean pragmatic one. Yes, sometimes I do backend. Yes, in Kotlin, Javascript or even Swift.