Daily coverage of Apple’s WWDC 2019 conference, by John Sundell.

A Swift by Sundell spin-off.

SwiftUI’s relationship to UIKit and AppKit

SwiftUI, Apple’s brand new declarative UI framework, is here — and it’s exciting. But at the same time, looking at SwiftUI’s API and watching the various sessions on it can also start to raise some questions — like, is this a complete UIKit and AppKit replacement, how does SwiftUI views actually get rendered, and is it all just a simple “wrapper” around Apple’s previous UI frameworks?

While it’s quite easy to initially look at SwiftUI as just a declarative API written right on top of UIKit (and in some ways it is), it’s so much bigger than that. While it’s true that, at the end of the day, any SwiftUI view hierarchy will get resolved into some form of UIKit/AppKit/CoreAnimation representation — there’s not a simple 1-to-1 mapping going on here.

The true power of a declarative framework, like SwiftUI, is that it allows the system to make certain decisions on behalf of the programmer. Things like what exact set of views to use to represent a UI description, what margins to default to, how to set up a base set of accessibility properties, how to handle the switch from light to dark mode, etc. All those things, and many more, can now in many cases be handled by a powerful and consistent set of defaults, rather than having to be explicitly programmed. It’s like the classic Ruby on Rails motto — convention over configuration.

For example, let’s take a look at this SwiftUI hierarchy, in which we wrap a set of horizontal stacks (defined using HStack) inside of a vertical one (using VStack):


struct ContentView: View {
    var body: some View {
        VStack {
            ForEach(0..<3) { _ in
                HStack {
                    Text("Leading")
                    Text("Trailing")
                }
            }
        }
    }
}
                    

Looking at the above code sample, it’s easy to jump to the conclusion that the resulting UI will consist of a vertically aligned UIStackView — which’ll then contain a set of horizontally aligned, nested stack views, which in turn each will contain two labels rendered using UILabel. However, none of those assumptions are actually true — as SwiftUI instead draws our labels directly using the private CGDrawingView class.

To understand what’s actually going on, we need to try not to immediately start mapping what we see in this new API to concepts that we’re already familiar with. Common UIKit or AppKit wisdom, like “Don’t use too many stack views” or “Always set a background color of your views when you can, since it’ll avoid blending”, is not really applicable here — since SwiftUI is not actually just rendering an exact copy of the hierarchy we define.

Instead of looking at SwiftUI hierarchies as actual views, it’s probably easier to look at them like descriptions. Since we’re now in a declarative world, we’re no longer giving direct, imperative instructions to the system — we’re instead describing what we’d like the end result to be. In fact, much of the SwiftUI documentation doesn’t refer to its built-in View implementations as ”views” — it refers to them as ”tokens”. So when you build your own SwiftUI views, and define stacks, texts, navigation buttons, and so on — think of that as declaring tokens that’ll represent the hierarchy of your UI — not as writing actual rendering code.

So trying to look at SwiftUI with a “fresh pair of eyes” can definitely help in trying to understand this fascinating new framework. But at the same time, it’s important to remember that — even if this is a new framework, and a new programming paradigm for many — the same set of good engineering practices that we’ve used to build UIKit and AppKit apps, still apply to SwiftUI as well.

For example, looking at some of Apple’s code samples, and some of the initial uses of SwiftUI that we’re starting to see shared online — it’s quite common to start getting worried that this new API will always lead to a “Pyramid of Doom” scenario, when code becomes so heavily nested that it gets hard to read. However, there’s nothing about SwiftUI’s API that forces any developer to write code that’s hard to read. Just like when building apps using UIKit, AppKit, or any other UI framework — it’s not up to the framework to decide how we structure our code — it’s up to us!

As a brief demo, here are two ways (not the only two ways, but just two examples) of how the initial code sample in this article could be written to reduce the amount of indentation required:


// By extracting part of our hierarchy into its own, separate view:

struct ContentView: View {
    var body: some View {
        VStack {
            ForEach(0..<5) { _ in
                Row()
            }
        }
    }
}

struct Row: View {
    var body: some View {
        HStack {
            Text("Leading")
            Text("Trailing")
        }
    }
}

// By using an inline function to build our rows:

struct ContentView: View {
    var body: some View {
        func makeRow() -> some View {
            HStack {
                Text("Leading")
                Text("Trailing")
            }
        }

        return VStack {
            ForEach(0..<5) { _ in
                makeRow()
            }
        }
    }
}
                    

Like always, which solution that’ll be the most appropriate will most likely heavily vary depending on the scenario — and I’m sure that, as a community, we’ll start to discover a general set of “best practices” when it comes to structuring SwiftUI code as we start to actually use it in real projects.

My advice: Stay curious, experiment, but avoid jumping to conclusions, and remember that good engineering will always be good engineering — regardless of what UI framework that we’ll use, either now or in the future.

Thanks for reading! 🚀