Explainer Technical

Communicating state changes between UIKit and SwiftUI

It’s possible, and often necessary, to combine Apple’s legacy UI frameworks with their hottest new technology, SwiftUI. SwiftUI solves some of the most common problems Apple platform developers encounter, but it’s still a 1.0 technology. In the cases where does SwiftUI offer analogous components to UIKit or AppKit, it doesn’t always provide identical configuration options. Moreover, not all existing components have SwiftUI representations.

While the techniques for embedding SwiftUI and legacy view frameworks in one another are straightforward, one head-scratcher for new SwiftUI developers is how to make these views communicate. The old UI paradigm made every single view element its own object. To communicate between objects, you called the method you wanted and that was that.

SwiftUI, on the other hand, computes a view based on state data. Each view is a struct that describes things to be drawn on the screen, rather than an instance of an object. There’s nothing to call methods on.

Nonetheless, it’s possible to bring harmony to these two approaches in a way that is seamless to the end user, and straightforward enough to maintain for your team.

Personal Projects Technical

Designing, building and shipping an iPhone app in five weeks with SwiftUI

I was laid off last month.

I’m hardly alone in this. With COVID-19 upending the game boards of every industry under the sun, lots of us have lost jobs.

Of course, this makes finding my next act complicated. Even in the technology business, thousands of highly qualified professionals have been cut loose in recent weeks. It’s just not a great time to look for a job.

So I decided to go back into business for myself.

The first step in that process was building an app as both a source of income and a showcase of my abilities as a product designer and software developer. I’ve spent every day, night and weekend of the last five weeks designing, building and shipping an iPhone app. Part of what made this possible was SwiftUI. Apple’s new declarative UI framework makes building touch interfaces faster than ever. Even with the pitfalls you’d expect from a brand new technology, it is already the best and fastest way to build an app for iOS.

Choosing a product

Once upon a time, I fell in love with product design, code, and shipping software in Second Life. I didn’t think I’d do anything else with those skills—I just had a lot of fun with them, and paid a few bills on the side.

But in 2008, when the App Store debuted, I saw a chance to put those skills to work for a truly global audience. I wanted a career building software.

I knew no one would hire me without experience, so I set about giving myself some. I built a raft of products for the iPhone—a game, a travel organizer, a simple app for shopping at IKEA. I sold a few of each.

But there was one app that truly stuck. Called Tallymander, it was a flexible tool for counting and math. Early on, to my shock and exhilaration, Apple featured it on the front page of the App Store. I heard from customers across all walks of life—research scientists, drywall installers, collectible card game enthusiasts—who were in love with it and had feature suggestions.

Look at that shiny, glossy UI. Were we ever that innocent?

Eventually, I got my career as a professional product designer and iOS developer. In fact, Tallymander opened the door: the CEO at my first startup gig was an early Tallymander customer. But I didn’t have time to maintain my indie projects, so I took them off the store.

No one really minded. Except my Tallymander customers, who emailed, bereft, for months.

Like an estranged friend, I’ve thought of Tallymander constantly in the years since. I loved that product. I loved that it was useful to people. And I loved my tiny contribution to the Great Conversations of products like VisiCalc, Excel, and Airtable. All of these make data and algorithms something tangible, experimental and fun.

Over the years, I’ve wanted desperately to revisit Tallymander. But the level of effort it takes to build an iOS app is significant. Quality is paramount, for one thing. You have to do things well to thrive in that environment.

But UIKit, Apple’s original UI development framework, can be such a slog. It was hard to maintain the velocity needed to get a project off the ground.

Last month, I was given more time than I asked for. And SwiftUI, Apple’s replacement for UIKit, had gelled enough to build with.

This confluence of events meant one thing: it was time to bring Tallymander back.

SwiftUI: the fast, modern way to build an interface for Apple platforms

UIKit emerged for third-party developers in 2008. It was a very different world. There were only two models of iPhone. Both phones had identical screen sizes. The iPad was still a secret project, known only to a few.

In the years since, iOS evolved to accommodate a vast array of devices. You can use UIKit to target all of the different iPhones and iPads. Using tools like auto layout, you can fiddle your way to a view that looks right on any size screen. Eventually.

But in a lot of ways, UIKit and friends feel pretty creaky. Want a table-based UI? You’ll have to implement dozens of lines of data source and delegate boilerplate just to get started. Want to draw shapes? It’s an exercise in such tedium that an entire app exists to spare you the worst of it.

UIKit also let you create bugs if you weren’t careful. Cocoa Bindings never made the journey from macOS to iOS. With no reactive frameworks built in, a typical approach was to write UI values in one pass, maybe when the view appears, and read back any inputs in another, perhaps when it disappeared. This could work, but the space in between was room for bug city. Frameworks like RxSwift emerged to fill the gaps, but didn’t have the widespread, universal adoption of Apple’s many iOS libraries. You could cobble something together yourself with KVO, but managing subscribers was its own headache. Get it wrong, you get crashes.

Apple’s edge in mobile was multiplied dramatically by the existence of developer tools and frameworks adapted from Mac OS X. Preserving that edge meant investing in new developer-focused technologies, especially Swift, which could be fertile ground for a new generation of APIs.

That strategy is has borne some meaningful fruit in technologies like SwiftUI and Combine.

SwiftUI is declarative. You tell it what you want to see, it figures out the details for you. This is a departure from UIKit, where every point of every frame of every view was a detail the developer could worry about.

Tell SwiftUI you want a field here, a slider there, and a toggle switch if the slider exceeds a certain value, and you’ll just get that interface. Maybe it won’t be exactly what you had in mind, so you’ll add a few modifiers. Primp with some padding, polish with some rounded corners.

An entire form with conditional fields specified in under thirty lines. Some of these call out to additional, more complex views, but most of those are reused across this and other forms.

This is already pretty convenient. But SwiftUI debuted alongside Combine, Apple’s framework for “processing values over time.” In practical terms, Combine lets you easily, accurately keep track of everything from network request progress to the state of your user inputs, consistently reflecting that state in both your UI and your data model.

These two frameworks together make it ridiculously fast to build a UI. This operated on two levels:

  • Easy to experiment/prototype and quickly see results
  • Less time spent on debugging because things largely just worked

Building Tallymander from scratch, in Swift, I started out with UIKit and thought I might experiment with SwiftUI. It seemed like a good way to build detail views and forms.

By the time I finished, 95% of the app’s view code was SwiftUI. It was just so much better at solving the problems I was tackling, and so much more fun. Before bed, I’d imagine some new feature I wanted. With SwiftUI could often knock it out within an hour or so the next morning.

The almost-instant previewing of SwiftUI interfaces was a big factor here. Rather than building and launching to check sizing, typography or content, you can see in seconds whether your layouts and drawing code are doing what you expect. One thing that surprised me was how deep these previews can go. You can include anything in your application as input to a SwiftUI preview, and that includes Core Data entities. Set up a garbage context that writes to /dev/null and you can build arbitrarily complex object graphs to test your designs.

SwiftUI’s layout system takes some getting used to—I’d be lying if I said I’d come close to mastering its more advanced features. Still, the basics are so easy to learn, and get you quickly to a UI that easily scales to any screen size. It’s a big improvement on the fiddly autolayout experience of yore.

I was helped along the way by Thinking in SwiftUI. To give you an idea how early days we are, I started reading it while it was available chapter-by-chapter on a pre-release basis. It’s done cooking now and worth every penny. It’s a helpful guide to building a new mental model after years with UIKit.

One thing I appreciate about SwiftUI is how easy it is to construct novel UI components. Example: The reset control on a counter expands to cover its neighbor and present a confirmation message.

Could I have built this in UIKit? Of course. But I’m not sure I would have thought to try, because doing it would be so fiddly. SwiftUI’s state management is so tidy, it’s trivial to add a @State variable, attach it to a few views, and see how you like it. Putting things into layout based on state is as quick as an if statement—so is pulling them out. Tacking on an animation is simple, too. You can go from “what if…?” to working results in a couple of minutes, and then decide from there if you want to keep those results. More experimentation, more fun.

It’s a brand new technology, so there are occasional hiccups. Passing numerical values to TextFields—critical to any math-centric application—is a little buggy, and requires a workaround. But even with these bumps in the road, it’s a much better path to shipping.

Everything in the new Tallymander is drawn with SwiftUI. Aside from the icon assets, there are no pre-rendered images. This is an approach worth taking even in the old world of Quartz and UIKit. With multiple screen densities, and now Dark Mode, if you can afford to do your drawing in code, you’ll save yourself a mess of image variations. SwiftUI makes the economics of this so much more favorable, as you get a realtime preview of your efforts as you go, and the drawing API is significantly less verbose.

But what about iOS 12?

I say don’t even worry about it. My policy has been to target only the latest major OS version for new apps. iOS update adoption is aggressive, especially in the age of emojis. >90% of customers end up on the latest major release within six months, so why bother doubling your test burden? A small and shrinking slice of the market isn’t worth the headache of backwards compatibility in most cases.

This case is even more dramatic now, where it’s a choice between SwiftUI’s velocity and slogging through the mud of UIKit.

Old friends

I said 95% of Tallymander is SwiftUI. So what about the rest?

There was one place where I needed classic, UIKit-style control over the interface down to the quantum level: the actual table that houses your Counters and Calculators.

Right now, SwiftUI doesn’t expose a lot of customization for its analog to UITableView, List. But since this was going to be the core of people’s day-to-day experience of Tallymander, I wanted to be able to tweak everything about layout, cell rendering, edit behavior, you name it.

What’s wild about this, though, is that while the enclosing cells and tableview are UIKit, all of their content is SwiftUI. It’s possible to make them work together that seamlessly. Watch:

The Counter buttons that fade away, along with the bandage symbol, are all views in SwiftUI. They animate alongside the red delete controls on the left, and the reorder controls on the right, which are provided by UIKit.

But to the user, these details are invisible. Thus, where you need an escape hatch to UIKit or SwiftUI in your project, you’ve got one, and there’s no compromise in user experience. That’s powerful stuff.

Other modern niceties

Working with Swift is a joy. The rigid type system took some getting used to after years of Objective-C. But the reward for this temporary discomfort is fewer fucking bugs. According to TestFlight, Tallymander has never crashed in the wild. Not once. The only crashes I’ve observed were the immediate consequences of doing weird things with text that SwiftUI didn’t like.

This is a lot of time back in your pocket, once again, because you’re working on features instead of chasing bugs.

I also want to take a moment to celebrate a small feature in iOS 13. SF Symbols debuted at the last WWDC and it’s a game changer for teams large and small. By providing a vocabulary of 1500 symbols, developers now have a low friction way to enhance the communication of their UI. This was an enormous headache when I was starting out in 2008. The OS was filled with symbols that meant things—chevrons as “disclosure indicators,” little red gems to indicate delete controls, green dots for creation controls.

SF Symbols: Apple provides a Mac app as a directory of all the glyphs

If you wanted to put your own spin on these, you had to build them from scratch. If you needed your own symbols, you needed to design or procure them as well. An entire cottage industry formed around this—I supported the Glyphish Kickstarter because it promised a few hundred such symbols.

There was also friction with getting such images to the screen. You had to size and convert them, add them to your Xcode project, and with the advent of Retina displays, manage multiple resolutions.

With SF Symbols, it’s a one-liner: Image(systemName: "chevron.right"). It’s trivial to style your symbols with colors appropriate to your app design. If you’re using SwiftUI, composing them with other UI elements is a snap using ZStack and friends.

This makes it so much faster and easier to make a UI that’s clear and easy to understand. Because you’re using a shared vocabulary with the OS and other apps, users will feel at home with your iconography.

Open for business

With SwiftUI, it’s a great time to build iOS apps. The time costs are lower than ever, the APIs are fun and modern, and the results are satisfying. The new technologies have their quirks, but have already gelled to an impressive degree with less than a year in developer hands. I know this was very high-level, but rest assured I have a few practical tips I’ll be sharing soon, especially about how to make UIKit and SwiftUI work well together.

I hope you’ll check out the new Tallymander and share it with anyone you know who’d enjoy a tool like this.

I’ll be continuing to flesh out Tallymander, and plan out my next projects. If you need support for an iOS project on a freelance basis, drop me a line. I can give you the perspective and experience of someone who has not only been with this platform from the beginning, but who is also up to speed on the latest technologies.