Is SwiftUI a Good Idea?

There are two major options for developing iOS (and Mac) apps at this point. Cocoa/Storyboard and SwiftUI. The first is essentially a highly refined version of what NeXT shipped in 1987, while SwiftUI is an “it’s all code” approach with a kind of live-preview (but not really) set up to round-trip to your code (but not really).

Now, being skeptical of SwiftUI is not exactly a controversial stance. Consider this blog post, by Steve Curtis, written in May, entitled “SwiftUI Still Isn’t Production Ready”.

SwiftUI is a trade-off in what you can do against what we are able to do in UIKit. We also need to take account of team skill, project, timeline and other variables when thinking about which technology to use for any given task.

Here’s a comment on SwiftUI on a Reddit thread:

It’s good that SwiftUI is so beginner friendly, but at the same time it lacks flexibility for the high end, which is terrible

After kicking the tires of SwiftUI, I’m actually much less sanguine than Mr. Curtis about SwiftUI, and I’m not really convinced it’s “beginner-friendly” either. There’s an illusion of friendliness because if you change the font of a label in SwiftIUI, you’ve “written code” while in Storyboard you’ve just picked the new font from a menu, but in fact it takes more steps to do everything, lots of things are harder to find (or impossible because they haven’t been implemented yet), it’s less forgiving, and your new all-code layouts can crash.

Even if simple things like the attribute browser were better implemented (e.g. if it automatically filtered the list of attributes to those that applied to the thing you selected, rather than requiring you to text search for every damn thing) and the “live preview” actually stayed live without needing to constantly be restarted, and drag-and-drop actually worked consistently, I would still be leery.

But, my skepticism is more profound. I don’t think it’s the right way to go. I think it’s a just a bad idea. Make no mistake, coding with Storyboards and @IBAction and so forth is pretty clumsy, but SwiftUI is misguided.

If you lived through the GUI era that began, for most intents and purposes, with the original Mac (or, perhaps more accurately, with ResEdit), then you’ve seen a bunch of variations on a basic theme:

  1. Draw the user interface
  2. Write code to make the user interface work

Underlying this is a mental framework referred to as “model view controller” or MVC, where the thing you draw is the “view” and the code you write is the “model” and “controller”. Which is which tends towards one of two approaches:

  • The model is the business objects, the controller is glue code
  • The model is data, the controller is logic

In the former, model code is valuable and reusable and glue code is disposable. In the latter case the controller code is valuable and reusable, and there is little or no glue code (instead bindings are embedded in the view). This latter is sometimes referred to as MV-VC. b8rjs is an implementation of the latter.

With Storyboards you can draw links between the UI and the code that become @IBOutlet and @IBAction declarations in your code and declarations in the XIB file. These are bindings used, at run-time, to allow code to unambiguously refer to user interface elements (for displaying and updating data) and user interface elements to trigger event handlers, respectively. Under the hood, XML is written into the xib file (the XML file that records the UI that you drew) that says “this thing is referenced by that thing” and the compiler inserts symbols into the code to allow events to trigger bits of code.

All of this works really well if you do everything correctly and never make mistakes, but because under the hood it’s really just writing XML and Swift code and the two things are not actually linked, if you make changes bad things can happen really easily. E.g. if you rename an @IBAction it will no longer be “found” at runtime when the events that are supposed to trigger it occur.

Of course no-one ever does everything correctly and never makes mistakes, so I think it’s fair to say that all of this doesn’t work terribly well at all.

As an aside, I’m not completely sure how things work “under the hood” these days, but certainly in the “good old days” applications shipped with xibs and you could mess with them in shipping software (much like you could modify a Mac app’s resources and change the app’s appearance without touching any code). There are certainly arguments in favor of “transpiling” xibs into code before packaging an app (and there are, in my opinion stronger, arguments against it) but SwiftUI essentially bypasses the xib by rendering the UI from the code, and converting changes to the UI into changes in the code on-the-fly.

Avoiding the Binding Problem

All of this can be labelled “the binding problem”. You have code and you have a view and each needs to be able to unambiguously refer to bits of the other, but they are two separate things.

One option is to not make them separate things, e.g. instead of having what is effectively a drawing program that lets you draw pictures of user interfaces and a text editor that lets you write code you can make a single document that does both. In Visual Basic, for example, when you changed a button’s code, you were changing the properties of that button. If you deleted the button, the code disappeared.

Perhaps the closest analog to SwiftUI in recentish memory is Dreamweaver’s HTML round-tripping. Ask any web developer what they think of this and you’ll likely get an earful.

Doing things this way eliminates “the binding problem” by not having to bind things at all. It’s very easy to do simple things with such tools, but it’s harder (or impossible) to do complex things. E.g. if you have two user interfaces for a single thing, imagine an app that has different views for iPhone, iPad, and Mac, but all interact with the same underlying objects, if you end up with code that is intractably tied to the user interface representation then everything needs to be written separately for each view. This is a really common problem and it’s why the MVC model was created in the first place. Even in a single platform app, you’ll often have multiple views of the same thing and want to swap them out, merge them, separate them, or whatever.

Views tend to be highly volatile (this week the font changes, next week the buttons are rearranged), but models tend to be very carefully designed and evolve slowly. If there’s no separation of concerns, then every time the view changes, the model changes. This is bad.

Another problem is where do you put everything? The answer tends to be code. In the MVC model, the view tends to be represented by data, i.e. a highly structured, static, declarative representation of what the UI should “look like”. The model and controller pieces tend to be implemented as code. The first thing tends to be easy to analyze and check for errors, while the latter isn’t. So, if you decide to merge the view and controller (forget the model for the time being) then you’ve turned the UI into something that is tangled with the underlying logic and is no longer easy to analyze and check for errors. In simple terms, a view that is “drawn” tends not to crash. A view that is created on-the-fly from bespoke code, on the other hand, who knows?

My previous blog entry concerned a very quickly built SwiftUI-based app that lets me wrap a web app in a “native” iOS application. It’s only a dozen or so lines of code. Brilliant!

But, if I were to build the same thing as a Storyboard app, it’s even fewer lines of code:

import UIKit
import WebKit

class ViewController: UIViewController {
    @IBOutlet weak var webView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let url = URL(string:"https://textreceipts.com")
        let req = URLRequest(url: url!)
        webView.load(req)
    }
}

Note that SwiftUI’s WebView has a slightly more elegant API than WKWebView or the SwiftUI code would be longer. It also has nicer “out of the box” layout defaults than Storyboard, but it’s not like Apple couldn’t refine the UIKit APIs instead of going down this blind alley with SwiftUI. And with Swift’s extension feature, it would be very easy to add your own nicer API to WKWebView and say write, say:

webView.loadUrl(fromString: "https://textreceipts.com")

But, in an architecture with proper separation of concerns between code and presentation, like HTML, it would be zero lines of code. There should be nothing to crash.

<title>textreceipts.com</title>
<webview src="https://textreceipts.com"></webview>

Now, if HTML, like Storyboard, didn’t actually let you configure the url of a webview, you’d need to bind code to the view. In b8rjs this would look like this:

View (HTML):

<title>textreceipts.com</title>
<webview data-bind="prop(src)=app.url"></webview>

Controller (javascript):

b8r.reg.app = { url: "https://textreceipts.com" }

The binding here is app.url.

SwiftUI is being touted as the “new” way to build iOS (etc.) applications, but it’s another “it’s all code” and “look, you can copy and paste” paradigm. Even if it worked perfectly and did everything we need it to, it’s going to make code harder to refine and maintain. And so far, it doesn’t do what we need it to and the developer experience is not great.

P.S. if you thought “but hey, JSX works this way” my response would be “yes, yes it does”.