SwiftUI: Answering the Big Questions

Dino Trnka
Ministry of Programming — Technology
12 min readSep 29, 2021

In September 2019, iOS 13 was released to the public, which included a new UI framework called SwiftUI. This framework’s been around for almost two years now, so now is a good time to answer some of the popular questions in the iOS community:

  • What are the advantages of SwiftUI?
  • What are XCode Previews, and how do they speed up iOS development?
  • SwiftUI follows a declarative programming paradigm. What does this mean for iOS developers coming from UIKit, which is an imperative programming framework?
  • How can we build views and apply styling in SwiftUI?
  • Is SwiftUI production ready?
  • Can SwiftUI replace UIKit?
  • Should you switch to SwiftUI right now?

Let’s start by taking a look at some of the new features that come with SwiftUI. Surely, we can’t cover everything in a single article, but we’ll cover just enough so you can get a good picture of what to expect from this new framework. Feel free to write your own experiences and findings of SwiftUI in the comments section of the article!

What are the advantages of SwiftUI?

One of SwiftUI’s biggest advantages is the simplicity of creating and structuring UI controls, the ultimate goal being to “become the shortest path to a great app”. Because SwiftUI is the same API built into iOS, iPadOS, macOS, watchOS and tvOS, developers can quickly build native apps across all Apple platforms. This is achieved through many different design decisions and philosophies incorporated in the framework.

Some of these changes are simple and easy to figure out, while others can be quite a paradigm shift in the way we think and code, compared to the “old ways” of UIKit.

Before we get into the nitty-gritty, let’s first look into one of the most important new features for rapid native iOS development.

XCode Previews

Previews is a new SwiftUI-powered feature in XCode 11 which minimises the time needed to build, run and configure your views and allows you to focus on the UI creation itself.

We’ve all been there — the long and arduous code-feedback loop. You receive some designs for a new feature and after writing some code, you want to check out how it looks. So, you run the app in a simulator or on a physical device, navigate to the screen you’ve been working on and verify your work. But then you realise the padding on your button needs to be increased a little. So you modify the code, rebuild the project and run the app again. Navigate to the correct screen. Verify. Find another tiny thing that needs changing, such as font size, or perhaps a colour. Rebuild. Run. Navigate. Verify. Rinse and repeat.

The biggest problem with this approach? Instead of spending most of your time coding the app or receiving feedback, you’re mostly doing tedious operations such as building, running, configuring and navigating through your app to get in the state where you can verify if you’ve done a good job. There has to be a way to speed up this process, right? Luckily, XCode Previews allow you just that — to skip all the boring stuff and jump right to the most important (and fun) part, which is coding!

When you enable Previews, XCode builds your app in a specific way. It compiles the file you’re currently viewing separately from the rest of the app, injecting the implementation in the app. As the amount of code that needs to be recompiled after each change is a lot smaller than the rest of your app, XCode does this for every change that you make. And for changes that include literal values such as strings or numbers — no recompilation is required — XCode just injects the value in the app, providing instant “refresh-as-you-type” feedback!

You want to test how a specific view looks with different input data? Or maybe how it looks without any data at all? You’re curious how your app looks in dark mode? Or on different iPhone devices? All of this is not only possible but extremely simple to achieve with the Previews feature.

In the following example we can see how easily it is to generate multiple previews for the same view:

  • A preview that shows the view on a specific device (iPhone 6s),
  • A preview that shows how the view looks in dark mode,
  • A preview that is clipped to 300x150 pixels,
  • A preview that is shrunk to the smallest size that fits the view content.

If you know how to write a SwiftUI view, you know how to make a preview component. You just need to conform to a PreviewProvider protocol, which is a part of the SwiftUI framework. This means you need to import SwiftUI to use it. Keep in mind that due to SwiftUI’s integration with other Apple frameworks, you can use Previews in UIKit, AppKit and WatchKit all the same as you would in SwiftUI. This protocol only has one requirement: You need to provide a previews property that will hold your content. What you put in there is up to you.

As XCode Previews are literally running your code, you can use any custom logic or context in your previews, and even inject runtime configurations such as UserDefaults or environment objects. The preview will always stay in sync with your view, which allows you to try out different configurations extremely quickly.

Declarative vs Imperative programming

Before SwiftUI, developers had UIKit, AppKit and WatchKit UI frameworks that use imperative programming paradigm. SwiftUI on the other hand uses declarative programming paradigm. So, what’s the difference?

Imperative programming is a programming paradigm that uses statements that change a program’s state. An imperative program consists of commands for the computer to perform. It focuses on describing how a program operates, by implementing algorithms in explicit steps.

Declarative programming is a programming paradigm in which programs describe their desired results without explicitly listing commands that must be performed. It focuses on the what rather than on the how.

Simply put, in imperative programming, you need to provide a step-by-step instruction set required to fix an issue. In declarative programming, all you care about is that the issue is fixed — you don’t care how!

Of course, in order to make this possible, the framework must provide a domain-specific language (DSL) that can do the heavy lifting and provide the end result for you.

There are many advantages associated with declarative programming.
Some of them are:

  • Code readability: Declarative code favours the use of higher-order functions by discouraging low-level constructs, such as variables, loops, conditionals and assignments, which makes the code more understandable, more scalable and easier to learn.
  • Code Reuse: It’s much easier to reuse declarative code that only works with end states than code that was written using imperative constructs.
  • Less code: Large portions of boilerplate code are abstracted by the DSL, leaving fewer lines of code to do the same work.
  • Minimal mutability: Immutable data structures help in eliminating hard-to-detect bugs and are easier to handle.
  • Fewer errors: The fact that declarative programming effectively hides the low-level imperative details of the implementation means that fewer errors will occur, as many low-level details of the system are simply not accessible to the developer.

If you have experience with React Native, you’ll notice that working with SwiftUI is very much alike! I would even argue that with SwiftUI you get the best of both worlds, as developing native iOS apps now combines the simplicity and speed of React Native with the robustness of Swift, as a type-safe and strongly typed language, which means more speed and more control.

An example of a successful declarative programming tool is the relational database. It provides a domain-specific language named SQL, which hides the lower-level layer from the user, focuses on what must be retrieved and does so quickly.

Let’s give a concrete SwiftUI example of this declarative paradigm. In SwiftUI, if you add or remove a row from a list view, SwiftUI will automatically diff changes in your data collection, synthesise insertions and deletions and render them with their default render animations. You aren’t required to write any additional code, and you don’t need to manage the persistent data state yourself. That’s the power of declarative code!

Building views

SwiftUI views are defined declaratively, as a function of their inputs. Whenever an input changes, SwiftUI will fetch an updated version of the view. An updated version will be shown just based on what’s changed.

Instead of building our views piece by piece as we did in UIKit, we can initialise them as a complete code structure. To add subviews in container views, instead of calling functions such as addSubview(), we simply list the contents of the container view in a special closure, known as ViewBuilder.

Some of the simplest container views in SwiftUI are HStack and VStack, which place their subviews in a horizontal or a vertical stack, respectively. Other containers include ZStack, ScrollView, Toggle, List and many others. Even a Button control is a container — it can contain a Text, Image or anything else as a tappable view!

For example, the code for creating a view that contains an image control and a text control placed vertically one above the other is as simple as this:

VStack {
Image("ImageName")
Text("Some text")
}

We can immediately see that it takes far less lines of code to achieve this than it was in UIKit, and the code structure itself gives us a good idea on how this view will look in the app. This syntax is very clear and easy to read, and the usage of braces and indentation allows us to differentiate between container views and the views that represent content. This applies to all controls, since most of the controls in SwiftUI are also containers.

Just stacking controls is not enough for a good UI. Your text controls need to have a font and a colour. Your screens need a background colour. Your buttons need paddings. Your table rows need spacing. Your images might need a corner radius. And maybe you want shadows or even animations!

How to do this in a straightforward, SwiftUI way? The answer is…

View Modifiers

Simply put, a view modifier creates a new modified view from an existing view. View modifiers are extremely powerful, and you can write as many of them as you want without worrying about performance as SwiftUI collapses them behind the scenes efficiently.

You can also chain them together. Here’s an example:

HStack {
Image("Image")
.frame(width: 44)
.padding(10)
Text("Some text")
.font(Font.custom("System", size: 10))
.foregroundColor(.black)
}
.padding(.horizontal, 10)
.padding(.vertical, 5)
.frame(height: 60)
.background(.primary)
.cornerRadius(10)

View modifiers are lightweight and optimised, because their storage for properties is distributed through the view hierarchy, instead of all properties being inherited by every individual view as you would have in standard class inheritance.

Order of view modifiers is important, as each one works on the resulting view that has been created from all the applied modifiers above. Of course, you can also create and reuse custom view modifiers:

VStack {
. . .
}
.modifier(MyCustomModifier())

All of these mechanisms together enforce a simple rule to follow: You should prefer small, single-purpose views! Then create bigger, more complex views using composition. The entire point is to compose small pieces to create a larger view.

Here’s an example of a simple custom view that represents a chat cell. This view contains an image control that shows the user’s avatar, a text control that shows their last message, and circular indicators for their online status and whether the message was read or not. On the left side, we can see how this looks in SwiftUI code, and on the right side, we can see our cells in action via XCode Previews, each preview cell containing different input data.

As you can see from the example, the SwiftUI ViewBuilder syntax allows you to use conditionals easily in your declarative code, and it all feels very natural. You should push your conditions into view modifiers as much as possible to get the best results.

Using UIKit in SwiftUI

SwiftUI is a new framework, and even with all the advantages it brings to the table, UIKit is the “big brother” of iOS development, and it’s not going away anytime soon. Although SwiftUI gets new features rapidly, you will surely come to a point where you are unable to create something in SwiftUI, which you could achieve in UIKit.

Is this a show-stopper? Not at all! You can embed UIKit views inside the SwiftUI view hierarchy by using a protocol called a UIViewRepresentable. By adopting this protocol, you can make UIKit views whose creation and update processes parallel those of SwiftUI views.

Conclusion

Now that we have a much clearer picture of SwiftUI and some of the advantages it introduced, let’s answer the rest of the questions asked at the beginning of the article.

Is SwiftUI production ready?

I’ve been using SwiftUI professionally for about a year now, and yes, the app I’m working on is live on the AppStore. So, in that regard, I can say that SwiftUI can indeed be used in a production app.

However, we did build the app from scratch in SwiftUI, so it was not a migration from old UIKit legacy code. It was a bold and risky decision to go with SwiftUI on a new project when the framework was in its inception, but today I can safely say it paid off completely. Using SwiftUI skyrocketed the speed of our development and today the project is in a very good state. There were some drawbacks though:

  • We had to limit our users to iOS 13 and later. Soon, this won’t be an issue, as more and more users are adopting new iOS versions.
  • The compiler can sometimes show very obscure error messages that can be hard to debug if you‘ve never encountered them before.
  • It took some time to figure out and implement the optimal project architecture, but today new features are easily integrated into the app.
  • There were some things SwiftUI couldn’t do, but we solved that by wrapping a UIKit solution in a SwiftUI view by using UIViewRepresentable.
  • If you use a LazyVStack inside a ScrollView to show long lists, you will encounter some serious jittering. This can be avoided by using the List control instead, but it is far from ideal, and it’s up to Apple to fix this issue in the next SwiftUI release.
  • SwiftUI has a much poorer API on iOS 13 than it does on iOS 14, so in some instances we needed to manually code some controls for iOS 13 devices, such as LazyVGrid, ProgressView and ScrollViewReader.

Bottom line is that we were able to solve all of the problems we faced, and as time goes by Apple will provide a better and more stable framework which will make app development easier and more fun.

Can SwiftUI replace UIKit?

SwiftUI is without a doubt going to become the future of development for all Apple’s platforms and you should definitely get busy learning it. However, it will take a long time to gain the adoption that UIKit currently has. The truth is that UIKit is a mature framework that’s been developed, thoroughly tested and continuously improved for a long time.

So, it’s best not to think of SwiftUI as a replacement, but more as a welcome addition that can make our life easier. And if you’re trying to land a job as an iOS developer, UIKit knowledge is absolutely mandatory because that’s what existing codebases use.

Should you switch to SwiftUI right now?

It’s hard to provide a straightforward answer to this question. If you are planning to start a brand new iOS project, by all means go for it and start with SwiftUI — you will not regret it.

However, if you already have a big codebase built with UIKit, it will probably not be a simple task to migrate. Sure, in SwiftUI you can find matching UI controls to most of their UIKit counterparts, but the real difficulty lies in the fact that you are supposed to switch from an imperative coding paradigm to a completely different declarative one, so the difficulty of this task largely depends on the size and complexity of your project. All in all, I would strongly suggest you at least consider the switch and start by replacing individual views in your app with SwiftUI.

Another potential approach is to use SwiftUI only for the new features in your app. This is possible even if you’re not dropping iOS versions below 12, because you can still use the availability APIs to write views that are available only on the iOS versions that support SwiftUI. This will make your code future-proof and you won’t need to rewrite it in a few years.

Further reading

In this article, we only scratched the surface of SwiftUI and answered some of the most common questions associated with it. There are many more important topics you should check out if you’re interested in SwiftUI development, such as animations, navigation, state management, design patterns and MVVM.

Here are some useful articles you can check out to get some more insight:

--

--

Dino Trnka
Ministry of Programming — Technology

Programmer, gamer, musician and all-around geek. I get hyped easily and love to share it with others.