First Impressions of SwiftUI from the React Native Trenches

I’ll go ahead and admit this up front. I have a bit of a bias for the Apple developer ecosystem. Much of that bias has been earned honestly, though, from many years of using and making a living with their products. For the past 12+ years, for example, I’ve been primarily developing apps for their iOS and macOS SDKs. Even the Android apps I work on are built with tools on my Mac. Before mobile, when I was a full-stack web developer, I got away with working mostly on MacBooks using Mac versions of things like MAMP for PHP and MySQL development, and the Java SDK for creating JSP and Servlets. Before the web, I developed multimedia CD-ROMs in Macromedia Director on Macintosh PowerBooks and Quadras. And for my very first real job in college, I was a desktop publisher, working in PageMaker on a Macintosh SE/30—which I still have a working model of sitting on my bookshelf today.

My Old High School Computer Lab (Brand new Apple IIe’s were lined up off camera along the sides of the room)

Even back in high school, my school’s computer lab was painted with Apple logos and rainbows all over the walls while we sat in front of Apple IIe’s programming in Apple Pascal.

But as much as I still admire Apple’s ecosystem, I get really excited about any technology that can help build better apps and better user experiences. The options for mobile development have grown incredibly since the first iPhone SDK was released in 2008. Android has been putting out versions of their OS with truly admirable user interfaces for years. JavaScript has long since spread everywhere with front-end frameworks that can rival desktop applications. Even 3D gaming frameworks can be used to make cross-platform mobile apps. And lately, this idea of reactive and declarative programming has been challenging our ideas of how to write software in surprisingly new ways. One of the more popular, of course, has been React Native—which I ended up working with recently, for about three years.

 

Down in the Trenches of React Native

React Native is certainly a serious option to consider for building mobile apps. Companies have been understandably excited about it, and its cross-platform promises. The company I’ve worked at, for example, used it to deploy their first mobile app for both Android and iOS using almost 90% shared code. That’s a pretty compiling outcome.

However, having my long history with the iOS SDK and Apple’s rich development ecosystem, I often found myself a bit disappointed with some of its shortcomings. Things like…

React Native’s Wild and Woolly State

It didn’t go without notice, when I first started learning React Native, that people kept describing it as a “Wild West.” And sure enough, as I started using it myself, I often noticed I was wrestling with how and where to best handle state. Should I be placing my code in componentDidUpdate, or would it be better in shouldComponentUpdate? What are the performance implications? Why are my state functions getting so long? And why aren’t there better design patterns established for all of this yet?

Even after three years of working with it, I still hadn’t found a perfect approach I completely liked for building a React Native app. Especially when Redux was involved. And it seems I wasn’t alone. But it’s a fun challenge I continued to re-evaluate with every bit of new React Native code I get to write.

JavaScript Development Tools

There are a lot of things to like about JavaScript as a language. One of my favorites is how much JSON is part of its DNA. It makes working with API calls particularly elegant. There are some serious issues, however, with the language that make you scratch your head (this, I’m looking at you). But I’ll leave those kinds of criticisms and analysis to the many who have already done so.

What I do really miss, though, is a good IDE, and all the things that come with it. Things like a real compiler that can tell me when and where I have misspelled a variable name. Which I do a lot. And, of course, things like having a truly integrated debugger.

Quality of life as a developer can almost always be tied directly to the quality of our development tools. Especially when we have to work with them hour after hour, day after day. After all, when we, as developers, talk about building quality commercial apps for our customers, the importance of a good user experience is always an integral part of that process. So why shouldn’t our development tools get the same level of attention to their user experiences, too?

Xcode has its problems. But with React Native, I find the tools to be particularly disparate and kludgy. I know a lot of people speak highly of VS Code. I hear many even call it an IDE. As code editors go, it is pretty darn good. And it’s what the company I’ve work with settled on too. But it’s no mobile IDE. Not if I still have to use Chrome as my debugger. Which, to be honest, can sometimes feel like not having a debugger at all.

All I know is that when I am working in VS Code and writing JavaScript, it often makes me miss working in Xcode and writing Swift. Or working in Android Studio, writing Kotlin.

Flexbox and an Animated UI

React Native’s use of Flexbox (and its particular implementation of styles) is pretty powerful. For the most part, it adapts well to different screen sizes, allows for fairly complex UI designs, and usually works the same across iOS and Android platforms. Like many, I generally find it much nicer than, say, auto layouts in Xcode.

It does seem to suffer, though, from the same kinds of challenges its cousin, CSS for the web, does. It feels awkward and abstract having to apply styles to views in that way. There’s no syntax or compile-time validation. And styles don’t always apply the way you expect them to. Plus, React Native’s styles lack a big advantage of CSS for the web: no cascading.

Animating UI in React Native is also more awkward than I had hoped—especially when asynchronous tasks are involved. This can be equally as true for native iOS and Android apps, but I had high hopes, when I first started reading about things like React Native’s state-based LayoutAnimation, that it might help solve some of the tougher problems. In practice, however, it didn’t turn out that way. You often have to resort back to using more explicit view animations, and then integrate your own checks for when those animations are running and when they are complete. It’s doable (mostly), but it makes for some fairly brittle and confusing code.

Version Fatigue

This is a big one, for me. People make fun of how React Native still hasn’t reached version 1.0 status yet. In practical terms, though, React Native’s ever-changing updates make for some real-world headaches. Whenever you need to upgraded to a newer version of React Native for your app, it’s never a decision you make lightly. And when you do take the plunge, it’s almost always a multi-team effort requiring a full sprint or two to complete and test.

This, of course, is because it’s not only the core React Native frameworks you are upgrading (which have their own complex issues). It’s also the many, MANY third-party dependencies and frameworks in your project that will cause you real headaches too—and your project will almost always need to have third-party dependencies. Resilient state management, for one, is simply not sufficient in React Native yet (as of this writing). You need third-party libraries like Redux to help you build robust, professional applications. And there are plenty of other libraries that will be necessary too. With each new upgrade, those dependencies will get out of sync. Sometimes in painful, and time-consuming ways.

This is one of the main reasons why, if I’m ever in a lead or architect role, I work especially hard to avoid introducing any other dependencies into the project unless they’re absolutely necessary.

It’s true, every software project, no matter the technology or environment, has its upgrade and dependency challenges. But with React Native it often seems particularly troublesome for the entire team.

Performance Anxiety

In all honesty, it is incredible what the React Native engineers have done in regards to performance, and it continues to improve with each new iteration. There are (and likely always will be) limits, though, to how well a React Native app can run. Actual native apps continue to out-perform React Native apps. Often, by a lot.

Try building a responsive, dynamic data visualization feature in React Native, for example. You’ll hit the performance wall pretty quickly. This becomes a real issue when you have to explain to your product manager why a UI feature stutters or fails the way it does, or that it may not even be possible to implement at all.

Along Comes SwiftUI

So when Apple surprised everyone with their announcement of SwiftUI last year (2019), I was one of the many who couldn’t wait to give it a spin.

There is one obvious elephant in the room, of course, before continuing with any kind of comparisons from here. SwiftUI clearly has one major disadvantage to React Native: It doesn’t do Android. But if we can put that glaring detail aside for just a bit, the picture gets pretty interesting.

The Playground Where I Play (And Learn)

Wunderverse, a commercial app for iOS and Mac, is my playground for keeping my skills current.

I have a commercial app called Wunderverse that I maintain and update regularly in my off-hours. It’s mostly for fun, but it has a real and active user-base to support, so I get to use Wunderverse as a practical way to keep my real-world skills up-to-date, including all those things necessary to support a production app.

I first created Wunderverse in 2014 in Objective-C for iPad, before Swift had been announced. When it seemed like Swift had matured enough in 2018, I used Wunderverse to learn Swift by completely re-writing the app in Swift 4. Then again, in Swift 5. It’s also how I’ve gained a much deeper understanding of things like In-App Purchase, Subscriptions, Receipt Validation, CloudKit, GCD threading, and so much more. I even got to learn SpriteKit in the re-write so that I could create one common set of UI components for iPhone, iPad, and a new Mac version.

The Wunderverse Story Map editor, developed entirely in SpriteKit.

Using SpriteKit for the UI actually worked out pretty well. It meant I had to create a complete UI component library from scratch first (including things like Scroll Views and Table List components)  since nothing like that existed in SpriteKit. But all of that was really fun and instructive. When I was done, I had a new version of Wunderverse I could not only offer for iPhone and iPad, but for Mac too—all using the same shared code base.

There was one big problem with my custom SpriteKit UI library, though. It lost almost all of Apple’s built-in support for accessibility that the previous Objective-C version had inherited by simply using Apple’s native UIKit components. That was a serious bummer. And a big loss. After the release, users of the app were actually reaching out, asking why they lost all their accessibility access. Not good.

Re-Writing Wunderverse, Once again

So now that SwiftUI is a real option, I have a pretty compelling reason to learn it, now, the same way I learned Swift. By re-writing Wunderverse once more. This actually makes sense to me for a number of reasons, but mainly so I can try and accomplish these important personal goals along the way:

  • Gain A Solid Understanding of SwiftUI: The number-one goal of the exercise is to start develop a deep understand of SwiftUI, and all the best practices for developing with it.

  • More Experience Implementing Swift Best Practices: The current Swift code in Wunderverse is… just OK. Swift was new to me when I started re-writing it back then. I made mistakes and I cut corners. A new re-write would be a great opportunity to re-evaluate that code and to try and incorporate all the best practices I’ve learned over the past few years.

  • A Better Multi-platform Code Library: As before with my SpriteKit custom UI library, I’ll need a common library of code that can be shared across iPhone, iPad and Mac to make it easier to maintain and support the app for customers. So I plan to make it a Swift Package to gain more experience writing libraries, and perhaps offer it to others if it goes well.

  • Incorporating Solid Accessibility Throughout: This will also be a great opportunity for me to learn how to incorporate proper accessibility into an app—something I have woefully been needing to learn for some time now.

  • Laying the Groundwork for New App Features: Finally, I’ll take this opportunity to do some fundamental architectural changes to support some important new features in Wunderverse that people have been asking for.

Baby Steps

Thankfully, I won’t need to build an entirely new UI library from the ground up this time. No scroll views or table lists to build from scratch. No input fields to re-invent. SwiftUI provides all of that, and in a much better way. But, as with all UI frameworks, I’ll still need a subset of common UI components and utility extensions that Swift and SwiftUI do not provide.

I had always wanted to make my SpriteKit UI library available as open-source, but I never got to a point where I was organized enough to do so. Plus, after finally understanding how important accessibility really is in a UI library, I knew it just wasn’t worthy of being shared.

This time, though, I really do want to shoot for having something I can offer as open-source. A SwiftUI foundational library other developers could use in their apps, too. It may turn out to be all a pipe dream, but at least I can practice my skills in creating clean, re-usable, well-written code that might be worthy of an open-source framework. It also makes approaching the re-write of Wunderverse much more manageable—one baby step at a time.

In my mind, this means I will be focusing on these things as I code:

  • Usability at the Point-of-Use: Is it easy to use the features of the library in you code? Is it easy to configure and customize as needed? Are they plug-and-play?

  • Clean and Understandable Code: Is the actual code in the library easy to read, review, and improve by anyone else?

  • Well Tested and Well Behaved: Will the features of the library be solid and reliable, running in all environments? Will they have good unit test coverage?

  • Well Documented: Are there code examples and good documentation on what the library features do and how to use them?

  • Well Packaged: Will it be easy to add the library to an existing Swift project? Here, I’ll get to learn and use the new Swift Package manager.

Choosing My First Component to Implement

With all that in mind, I decide the first SwiftUI component I would create would be a Pie Chart. Why a Pie Chart? I’d like my new shared library to have some rich graphing components available in it. Visualizing data almost always seems to come up in an app in one form or another, and it will be needed in Wunderverse too. This is also one of those performance areas where React Native can leave you disappointed, so I wanted to see how SwiftUI handles it.

Plus, it gives me a gentle first step into figuring out things like how animations can be implemented with SwiftUI, how I can add configuration options to components, and how I’ll support things like Dark and Light mode throughout the library.

Pie Chart Component Requirements

Here were my requirements as I set out to build this new component:

  • The Pie Chart’s data and configuration options should be easy to provide it.

  • Each segment of the pie should be selectable by the user so that the segment and its value can be highlighted. When selected, a segment should animate out nicely from the center, and the segment’s label and value should be displayed on top of it. Any other segment that was previously selected should animate back into the pie, and it’s label should disappear.

  • The color of each segment should be configurable.

  • The component should provide a legend that associates all the segment’s labels with their colors. The legend should be optional, so that the pie chart can be rendered without it, if desired. The legend should default to being hidden.

  • The Pie Chart should have a way for the user to configure the pie chart themselves, with control options to turn the legend on and off, and to show the labels of all the segments.

  • The component should support Light and Dark modes.

  • Sample data should be included with the component by default, if no data is provided, so that developers implementing the component can see a demo of it in action out of the box.



The Results

It was a lot of fun diving in and building this first component. I will admit, SwiftUI took a little getting used to. Especially coming to it after three years with React Native.

With just a little effort, though, and less than an afternoon of writing code, I had a fully functional Pie Chart that met all my requirements. With a little more work, it might actually be worthy of including in a shared library.

If you like, you can check out and run the Xcode project here:
https://github.com/smckee/swiftui-pie-chart-experiement


Overall Impressions of SwiftUI (so far)

More Elegant State Management

In React Native, state feels like an awkward add-on. Not like a core part of the paradigm it should be. There is no special support for it in the JavaScript language. Instead, you have to manage and update your state through a separate state object and a setState() function. Then, when state changes do come in to your components, you often need relatively large amounts of code in special lifecycle functions just to decide if and how your app should actually react to those changes.

In SwiftUI, state is explicit. Syntax is built right into the language to identify what is state and what is not. Here, for example, we see our settings is an @ObservedObject letting us know it is state that can change on us…

@ObservedObject var settings = PieChartSettings()

Then, instead of writing a lot of code to figure out when and where to react to that state, you simply use the value in your components, using normal Swift syntax. Here, we are using that settings state to see if we should show our config panel or not…

if self.settings.configPanelVisible {
    PieChartConfigPanel(settings: self.settings)
}

That’s all there is to it. Then, to update state, you simply assign it a new value. No need to go through some special function to update a special state object. You simply update the value directly. Here, we update the settings value when the user taps a button…

.onTapGesture {self.settings.configPanelVisible = true}

By updating this value to true (using normal Swift syntax) the Pie Chart will get re-render, with the configuration panel visible, all because SwiftUI knows you are using a state value to determine the visibility. Clean. Clear. Elegant.

Robust, Integrated Development Tools

The integrated coding, visual layout, and debugging tools in Xcode that React Native sorely needs.

XCode is not perfect. There are reasons to nitpick, and people do. But in its latest iterations, especially with the addition of SwiftUI support, the integrated development environment that XCode provides is unmatched.

The new SwiftUI Preview Canvas, alone, is a huge step in making declarative app development a whole lot of fun.

My only real gripe with Xcode is a bug it has had for a long (LONG) time with refreshing its autocomplete indexes, syntax highlighting, and compile error indications. It simply looses its ability to do so, causing you to restart Xcode.

In general, though, I find Xcode to be well-designed and well-thought out, with all the professional-grade tools you need to develop with. Something the React Native community could really use.

Elegant Layout & Style Control with Thread-Safe Animations

SwiftUI gives you some useful layout views (VStack, HStack, and ZStack) that you can group your child views in, and all your other layout and styling happens by adding modifiers (.frame, .padding, .background, .fill, etc.) that you apply directly to the child views inside those layout containers. No more disconnected styles, defined somewhere else, completely separate from your view code. It’s really that straightforward and simple. Even better, the styling is all compiled, validated, and syntax-checked along with the rest of your other code to better catch problems.

Then, by extracting and organizing all your custom views into their own files and view types, your code becomes clean, readable, and manageable. Of course, that’s not new to SwiftUI. Component organization is something React Native lets you do pretty well, too.

As for UI animations, I’m still learning. There are definitely quirks I still don’t understand. But with the Pie Chart component, I was able to implement almost all of its animations by simply wrapping state changes in withAnimation(). Behind the scenes, SwiftUI took care of all the rest in a safe and resilient way.

Go ahead. Download the project and try to monkey-break the animations with all the taps you can throw at it. I haven’t been able to break it yet.

Manageable Dependencies

Unlike React Native, much of what you need to develop a complete, professional app is already built into the OS and SDK that Apple provides. You don’t need to use a third-party library to help you manage state, for example, because it’s already implemented really well into the SwiftUI language. You don’t need a third-party library to help you manage the side effects of asynchronous tasks, because you have the existing GCD and Combine frameworks to help.

The only dependencies you need, are the things that are unique to your app—specialized UI elements, custom data storage and APIs, encryption and compression libraries, etc.

This all makes for a much more manageable—and predictable—upgrade lifecycle. Plus Xcode now supports Swift Packages—a huge improvement over CocoaPods and Carthage—which, in themselves, were just additional dependencies that could break on you.

Native Performance

I haven’t worked with SwiftUI enough, yet, to really judge performance. With my simple experiments so far, though, I’ve been more than happy with how the UI has been responding. Knowing that you are working directly in Swift, making calls directly to the native OS libraries, plus the fact that it’s so easy to integrate non-SwiftUI views in your UI too, I’m hopeful it can handle anything a native app can. I’ll be keeping my eye on that.

Conclusion?

An early POC of the new Wunderverse Story Browser in SwiftUI.

It’s still too early to tell for sure. SwiftUI has some growing up to do, and I need to spend a lot more time with it to better understand all the subtleties. But it feels very promising—like it has the potential to be a lot of what React Native wishes it could be. Well, except that whole “being-able-to-run-on-Android” part…

I hope to have time to post more in the future about my experience re-writing Wunderverse with SwiftUI. In the mean time, I’ve already started experimenting with a new Story Browser, and I am really liking where this could go!

Previous
Previous

Switching From React Native to Proper iOS and Android Deployment

Next
Next

Honoring the Ways of the Original Macintosh