I worked from home on June 3 and watched the WWDC 2019 Keynote live from my living room. As it was getting towards the end, it already felt like the most exciting WWDC keynote Apple has had in years.
And then Craig Federighi announced SwiftUI.
I had been wanting Apple to release a functional, reactive UI framework for their platforms for years. It finally happened.
I have been coding React for about five years. React changed the way I thought about building product. Never before was I able to build UIs so quickly. React spoiled me. I started to find it incredibly difficult to work on platforms that didn’t use similar paradigms (like for example, iOS/UIKit), because I felt so slow.
SwiftUI is pretty similar to React in a lot of ways. They’re both functional, reactive-ey, and declarative. But more importantly, the high-level purpose of these two projects are the same. Both are there to enable a 10x speedup in development speed over traditional, imperative UI frameworks.
Why do I find building products with React so much faster than other MVC-style UI frameworks? A few reasons:
I’ve been playing around with SwiftUI for a few weeks trying to build a new app. Unfortunately, it wasn’t the experience I was hoping for. I’ve been constantly running into limitations, quirks, and bugs that have slowed me down tremendously. Throughout this experience, I’ve been trying to give SwiftUI the benefit of the doubt and thinking that maybe I have been doing some things incorrectly. But my patience has run out. The entire reason I was excited to use SwiftUI – improved development speed – is just not being delivered. In reality, it seems that working with SwiftUI is much slower than if I actually just used regular UIKit.
I’ve gone and submitted Feedback™ for all of the acute issues I’ve run into (see this twitter thread for a summary). I’m really hoping, for SwiftUI’s sake, that most of these issues get resolved sometime in the next few months. But I’m not counting on it.
Even if all of these issues are resolved, I still have several deeper concerns for SwiftUI. How frequently will it get updated? Will SwiftUI grow a healthy community like React did? How active will the SwiftUI maintainers be in the community? Will it ever not feel so limiting and frustrating?
I tend to roll my eyes whenever an engineer scoffs at a framework/library because to them it seems “magical.” Just because something uses an unfamiliar pattern or abstraction doesn’t mean that it’s bad. I think what people really mean when they complain that some framework is “magical” is that it is an appealing abstraction that’s also limiting. In other words, it may solve for 99% of use cases really well, but when someone has to build something that falls into that 1% category, what will they do?
Five years ago, my team at Yahoo started pitching the idea of switching to React to my then-manager. He was skeptical because React seemed “magical,” and usually that means that if you run into a limitation, then you’re out of luck. But this wasn’t really a problem – React had a great story around being able to escape out of the React lifecycle and do things manually. Not even that, but you could elegantly encapsulate these sorts of escape hatches inside Components.
SwiftUI does not provide this same sort of experience. Unlike React, I find SwiftUI “magical” by the definition that most people use the word. It feels highly constraining. You’ll realize from working with it that there are many things it doesn’t support, and there’s no real way to implement these use cases yourselves. This is scary.
This experience with SwiftUI has given me a renewed appreciation for React. I hope that the SwiftUI team looks at React for more inspiration for how the framework and community can be improved. Here I’ve collected my top wish list items that I think will have a large impact in making SwiftUI feel just as much of a joy to work with as React.
React’s ref feature allows you to get an imperative handle on a component instance. Generally, using refs are discouraged, as new React developers will use them when instead they could write more declarative APIs. However, there are situations where that’s not possible.
For example, say you’re working with native elements. Using React refs, you can get the raw underlying instance of that element. This allows you to read and manipulate that native element however you please.
One discrete issue I had with SwiftUI was that I wanted to be able to pop views in my NavigationView. There is currently no API to do this. Now let’s imagine if SwiftUI had some concept like refs. Then even though there is no “native” SwiftUI API today, there is a backdoor that allows me to get the native UINavigationView instance, from which I can manually call
Some of the SwiftUI bugs I ran into had to do with state that persisted across updates, but ideally should’ve been invalidated. React has a built-in feature, keys, to signal that a subtree should be treated as new, and any pre-existing state should be discarded. (In the React docs, keys are only mentioned in terms of lists, but they are also useful in other situations when you want to invalidate all state in a given subtree.) If SwiftUI had some sort of similar mechanism, many of my head-banging-against-desk moments from the past few weeks would’ve been easily worked around.
React has a rather robust story when it comes to managing effects. (For the purpose of discussion here, an effect is simply a state transition or some sort of call to “IO,” like an API request). You can easily hook into the React runtime and update state or perform side-effects.
SwiftUI has a more limited story with regards to effects. You have the
onDisappear modifiers. But what about
onUpdate? What if I want to make an API request from a View not when it mounts, but when a value passed to it changes?
It seems like SwiftUI encourages moving a lot of this sort of effects management external from the UI with Combine,
@EnvironmentObject. If you model all of your view state with these sort of “view models,” then I suppose it’s not as much of a problem. Though, this does make
@Binding far more limited.
With React components, I tend to prefer writing controlled component APIs whenever possible. This allows the component consumer to have more control and flexibility to define how values get managed. It also makes it easier to debug behaviors.
PresentationLink seem really nifty (perhaps “magical”), but they come with a pretty big downside. Because these Views internally handle hooking up events and managing state, there’s no method for implementing any other sort of behavior. At least with there’s an alternative to
PresentationLink with the
presentation() view modifier.
But there is simply no way to control the navigation state of a NavigationView via a value. This is very concerning.
I just hope that eventually all behaviors are exposed as controlled APIs, and that these uncontrolled APIs like
PresentationLink quickly go out of style.
While ViewBuilders are getting a lot of praise for avoiding JSX-like syntax and nesting, they are far more complicated than React’s equivalent. With React,
children is simply a single element of an array. This makes it so easy to manipulate. Writing custom ViewBuilder code with SwiftUI is pretty painful and feels very constraining. I realize that some of this has to do with SwiftUI’s strict typing and limitations of the Swift language (i.e. variadic generics). But I’m hoping in the future there’s just generally more APIs to make it easier to implement more-complex ViewBuilder logic.
It’d be wonderful if there was a tool similar to the existing Xcode View Debugger, that allowed you to traverse the SwiftUI View hierarchy (which may not correspond to the actual layout/view hierarchy). Then you can inspect properties, state, and consumed environment objects for each of your SwiftUI Views.
I’ve seen how useful the React debugger is for new people jumping into a large codebase – just point and click and you know what View corresponds to some piece of UI and what state is used to derive that UI.
Not to mention, the current debugging story with SwiftUI is really poor. The debugger sometimes mismatches what is rendered, you’re limited in where you can put print statements, and exceptions often don’t come with any error messages.
This is perhaps the most important item in my list. Look at how the React team manages communication from the community. They participate in several conferences throughout the year. They seek out input from betas in an open forum on GitHub and Twitter. They spend a lot of time writing opinionated docs and tools to make it easy to learn and encourage best practices. In other words, SwiftUI really needs a Dan Abramov.
It’s early days. I knew that SwiftUI was going to have rough edges, but I would be lying if I said that I expected it to be this immature.
That said, I’m still incredibly excited about SwiftUI and still optimistic it will improve and mature over time. Looking forward to future updates, and more importantly, for a community to emerge around this exciting technology.
Thanks for reading this post! I’m still learning SwiftUI. Did I get anything wrong? What’s on your wish list? Ping me on Twitter.
Written by Ryan Ashcraft, a software engineer in the SF Bay Area. Follow @ryanashcraft on Twitter.