SwiftUI in late 2021

I've been reviewing the state of declarative UI frameworks such as Flutter and React Native, now we get to the third (and final) one, SwiftUI. Now is a good time to check in, as with the release of MacOS Monterey we can now use SwiftUI 3.0 on both iOS and MacOS.

On paper SwiftUI is terrible. There is an "And yet" coming but to give a somewhat objective viewpoint unfortunately this article will have to cover a lot of negative territory first! I apologise for when this comes across as a rant, but there are a bunch of specific, concrete issues which are in clear need of improvement.

SwiftUI Flutter React Native
Declarative UI
Hot (Stateful) Reload 😔
Compiles to Native
Reliable Performance
Interpreted/Preview Mode 🤦‍♂️
Not obviously buggy/incomplete 🤦‍♂️
Good tooling 🤦‍♂️
Cross Platform 🤔
Web Support
Ecosystem maturity
Modern language features

I've used SwiftUI for several Apps, some which have been updated repeatedly. The qualities of declarative UI which allow for composability and decomposition are very much in evidence. This becomes particularly clear when you come back to change or update something. But, frankly, in 2021 this is table stakes. SwiftUI as it exists today has a lot of problems. It is still better than using only UIKit (the legacy UI framework for iOS) or AppKit (MacOS), but it is frustrating that it is not a lot better.

Where to start?

The Terrible

I've had a couple of very basic features of Pattern Maker completely broken by minor iOS version updates. These were actually both quite simple, direct uses of basic functionalities: in one case NavigationLink and in another driving View state from a @Published enum in an ObservableObjected. The last one meant that a part of the app wasn't working for iOS 13.5 onwards (I tried rebuilding for 13.6 but they didn't fix the bug until iOS 14.0). Clearly Apple do not have a good story around QA and while that might have been just about acceptable for the first release, it is looking bad now.

The second terrible thing is DX, in particular Xcode. I suspect the development teams are under resourced and probably aren't even told about major new features they have to support. But let's be honest; the DX of Xcode is terrible. It has been terrible for years. We keep hoping it will get better, and parts of it do. But then some other stuff regresses. It is embarrassing how one of the biggest companies in the world, with an obvious stake in third parties being able to produce software for their devices, ships such a mess of a product. Especially around SwiftUI it is years behind competing approaches.

You will often see weird issues like:

weird xcode bugs

Often Xcode will fail to compile completely valid code and you end up rewriting in many different ways just to get one that compiles. This is a huge pain, especially as you keep getting distracted from the thing you are trying to do, by having to try and make something work. Autocompletion is also a joke. Given how static Swift is this could be a huge advantage. But when I write TypeScript, Dart or Kotlin I find helpful suggestions. When I write Swift, often nothing.

Xcode has a feature for SwiftUI called Previews. It is meant to achieve something like hot stateful reload from React Native or Flutter, but for small parts of your UI. It is bad.

First you will see lots of loading

Then you will make a change, sometimes small and:

paused

Then you run the App, and again:

paused

And sometimes after a lot of

loading

it just messes up completely and gives a useless error message.

weird error

Which might even go away if you clean you build.

Just how unreliable it is depends on Xcode release, but if you come from React Native or Flutter, expect disappointment.

paused

The Bad

There are lots of small ways in which Xcode is bad, that seem like incredibly low hanging fruit for Apple to fix. For example common tasks like adding an Icon, require you to fill in:

Yes, 30 or so separate bitmap images.

If you want to do a build for release you first have to select the 'Any iOS device' or similar target. Then go the build option.How does this make any sense?

The project configuration is terrifying, with hundreds of weird options exposed in obscure ways (that often lead to horrifying git conflicts). Swift has a well designed Package Manager that could surely take care of this in a less horrific way.

The okay

Incremental builds of small to medium sized apps are often fine; so the iteration cycle isn't always too bad. Performance of the simulator(s) are good (you are using M1, right?) and it looks fairly nice.

The Promising (in theory)

SwiftUI does have some cool ideas. The utility based View extension mechanism makes it simple and ergonomic to do all sorts of things with Views. In theory the type system leveraging UI approach is good for performance. The various property wrappers for state are pretty cool. Swift itself is a nice, if large, language, with lots of modern features, a nice syntax and is even tackling stuff like async code with relatively novel approaches like its new actor reference type. A lot of these are sadly held back by the reality of Xcode today.

Why is it like this?

Speculation, but it seems like two obvious factors drive this. One: SwiftUI is closed source, so fails to benefit from a wide community of developers with varying use cases correcting subtle bugs and moving things forwards. For something as complex as a UI framework this is a major disadvantage. Two: even for a closed source system development is very waterfall, based on annual releases, tied to OS versions and what appears from the outside like WWDC-driven development (their annual conference). At WWDC they need things make cool demos, then you discover that they barely work in practice or there are all sorts of incompatibilities.

Why, actually you might still want to use SwiftUI

That all seems negative, but there are a few reasons why SwiftUI might actually be great for your projects.

Apple Frameworks

There are some frameworks it gives you easy access to. For example say you want to make an app for iPhone, iPad and Mac that synchronises data. You can actually do this with iCloud and Core Data relatively easily. Or if you are making an app that uses specific frameworks for multimedia. These two areas has meant it has made sense for me for various creative coding projects and projects where I want to allow users to sync their own data on their own devices.

It also works well for MacOS specific apps that want to work with a particular file type (document based apps). It is surprising how much is taken care of for you there. Certainly there is good potential for developer tools and small utility apps. Status bar apps might also make sense, but given their limited UI, SwiftUI itself isn't of so much value.

Apple-y feel

For large companies (or even startups) applying their own look and feel and shipping to web, iOS and Android are likely to be priorities; so SwiftUI doesn't really make much sense as it stands. But for indie apps this can be good approach.

Performance

At least compared to React Native or Electron performance is much more straightforward. You can easily to explicit background work and the new stuff in Swift like actors, could be really useful for complex cases.

From UIKit or AppKit

If you have done a lot of either or both and know Swift already; it will be a lot easier to get started than trying to e.g. learn React Native. Indeed using e.g. a UIKit View in a SwiftUI App is way easier than using it in React Native. If your App already exists something similar applies.

Optimism?

All but one of the problems I outlined above seem addressable (although I would have said that a year ago and have been disappointed with Apple's progress). The one that seems fairly fundamental is the stateful hot reload thing; React Native and Flutter have from the beginning been set up to allow for this, it seems like it might be (nearly) impossible to add to SwiftUI. I suspect Apple are unlikely to target Android(!) so that also rules SwiftUI out of a large chunk of potential projects. Finally it is worth mentioning that Apple typically fail to apply new features to anything but the latest iOS and MacOS versions, again this greatly limits or delays use cases.