Weekly Swift articles, podcasts and tips by John Sundell.

Learning SwiftUI by building tools and prototypes

Published on 28 Jun 2020
Basics article available: SwiftUI

A year ago, before WWDC 2019 kicked off, there was a lot of speculation and debate within the Apple developer community as to whether Apple would finally start shipping Swift-only frameworks.

At the time, the notion that Apple would depart from its established strategy of maintaining full backward compatibility with Objective-C might’ve seemed like a quite radical idea, but nonetheless, that’s what ended up happening — with the introduction of frameworks like Combine, SwiftUI and CryptoKit.

Fast-forward to now, June of 2020, and the concept of Swift-only frameworks is now not just a possibility — it’s quite arguably the new normal, at least for new additions to Apple’s various SDKs. But Apple didn’t stop there. This year, at WWDC20, we saw the introduction of Apple’s first-ever SwiftUI-only framework — WidgetKit.

Now, depending on who you ask, the fact that the new home screen widgets that Apple announced during the WWDC20 keynote can only be built with SwiftUI might sound anywhere from amazing, to terrible, to downright scary. While it’s fair to say that SwiftUI has in many ways taken the Apple developer community by storm, and has enabled a lot of new developers to start building their first apps, the current opinions on SwiftUI are divisive to say the least.

However, whether you consider SwiftUI to be a fantastic new way of building apps, or something that’s not quite production-ready, one thing has been made quite clear — SwiftUI is here to stay.

So this week, let’s take a look at one possible venue for learning and exploring SwiftUI — one that I’ve personally taken during the past year — by building internal tools and various prototypes.

Learning doesn’t necessarily mean deploying

“Should I learn SwiftUI?” is, without a doubt, one of the most common questions that I’ve been asked over the past year.

Now, I always try to be incredibly careful when answering questions like that, since it’s very difficult to give any sort of concrete advice as to what any given developer should or shouldn’t do, without being familiar with their personal context. For example, what kind of app are they working on, what does their user base look like, and how does their team or company operate?

However, this year’s announcements from Apple have significantly shifted my default answer as to whether a developer working on Apple’s platforms should learn SwiftUI — towards simply being ”yes”.

It’s important to remember that you don’t have to deploy something within your main production code base in order to learn it. In fact, I personally try to do as much of my learning as possible outside of my shipping code, not just when it comes to SwiftUI, but in general.

While it’s often great to have a concrete use case in mind when learning something, there are so many ways to write highly valuable code without actually shipping it to customers, which heavily reduces the inherent risk associated with shipping apps or features using technologies that we haven’t yet fully familiarized ourselves with.

Building internal tools and prototypes

When it comes to SwiftUI in particular, its very lightweight syntax in many ways makes it ideal for tasks like prototyping, building internal tools and utilities, user testing, and hobby projects. For example, let’s say that we wanted to add a view to internal builds of our app that lets both developers and designers preview all colors within a set of themes, and that those themes are defined using a struct that looks like this:

struct Theme {
    var name: String
    var isDark: Bool
    var brandColor: UIColor
    var activeButtonColor: UIColor
    var titleColor: UIColor
    ...
}

To make those previews happen, we can use a bit of reflection to extract all of the colors from an instance of the above Theme type, and then render a list of them by writing just a tiny amount of SwiftUI code — like this:

#if INTERNAL_BUILD
struct ColorList {
    var entries = [(color: UIColor, name: String)]()
}

extension Theme {
    func makeColorList() -> ColorList {
        let mirror = Mirror(reflecting: self)
        var list = ColorList()

        for (label, value) in mirror.children {
            guard let color = value as? UIColor,
                  let name = label else {
                continue
            }

            list.entries.append((color, name))
        }

        return list
    }
}

struct ColorListView: View {
    var list: ColorList

    var body: some View {
        List(list.entries, id: \.name) { entry in
            HStack {
                Circle()
                    .fill(Color(entry.color))
                    .frame(width: 50, height: 50)
                Text(entry.name)
            }
        }
    }
}
#endif

Note how all of the above code is wrapped within a custom INTERNAL_BUILD compiler flag, which lets us avoid including that code in App Store builds of our app.

Let’s say that our app currently ships with two themes — one for light mode and one for dark mode — so let’s also add a list view that lets us select which of those two themes to preview. We’ll also modify each theme’s ColorListView instance to match its corresponding system color scheme, in order to make our previews even more accurate:

#if INTERNAL_BUILD
struct ThemeListView: View {
    var themes: [Theme] = [.light, .dark]

    var body: some View {
        NavigationView {
            List(themes, id: \.name) { theme in
                NavigationLink(theme.name,
                    destination: ColorListView(
                        list: theme.makeColorList()
                    )
                    .colorScheme(theme.isDark ? .dark : .light)
                    .navigationBarTitle(Text(theme.name),
                        displayMode: .inline
                    )
                )
            }
            .navigationBarTitle("Select a theme")
        }
    }
}
#endif

Note that if the above code was meant to go into production, or used to render a large list of themes, then we probably wouldn’t want to perform our makeColorList conversion inline within our view’s body. But that’s the beauty of building internal tools and prototypes — we can take shortcuts!

The above is of course just an example, but if we think about it, just by writing that small amount of SwiftUI code we’ve already had to learn about some of its fundamental concepts — like view composition, layout, modifiers, navigation, and so on. Plus, besides the learning aspect, we also ended up with a super useful tool for both the developers and designers on our team — pretty great!

SwiftUI is also excellent for prototyping. Just like when building an internal tool, we typically want to spend as little time as possible on any given prototype, since the point of doing prototyping to begin with is to quickly learn whether a given idea or feature is viable.

What makes SwiftUI so great in this context is that, since it relies on strong defaults and platform conventions, it lets us get down to building our prototype right away — with almost no setup required. Just look at the following code sample — that’s everything that’s now (as of Xcode 12) required to get started with a fully functional SwiftUI-based app:

@main struct MyPrototype: App {
    var body: some Scene {
        WindowGroup {
            Text("Let's start prototyping!")
        }
    }
}

Easing our way into production

Of course, SwiftUI is not just a prototyping tool, it’s a powerful UI framework that’s increasingly used to ship projects ranging from smaller indie apps to large-scale production software. So when and how could it be a good idea to use it within the code that we actually ship to our customers?

Besides it being a young framework, perhaps the most major limiting factor when it comes to SwiftUI adoption is the fact that it won’t run on operating systems older than iOS 13 or macOS Catalina — which might not be acceptable for a large number of projects, even though that’ll most likely change when Apple’s upcoming OS versions are released later this year.

However, as mentioned in last year’s post-WWDC article “Shifting paradigms in Swift”, SwiftUI is far from an “all-or-nothing” type of proposition. Its interoperability with UIKit and AppKit is really strong, meaning that we might be able to just use SwiftUI to build a small feature that we only make available to users running iOS 13 and above (which at this point should be the vast majority for most apps). Or, we might use it to implement features that rely on newer system APIs, such as widgets or app clips.

Just like how building tools, prototypes and hobby projects can let us learn a new framework in a “safer environment” — if we start adopting SwiftUI in just a small part of an existing app, we’re also likely to discover whether or not it’s production-ready for our particular use cases much quicker. Because even though both Apple and many third party developers are already deploying SwiftUI in production doesn’t mean that it’s going to be the perfect tool for every single app, at least not until it’s had more time to mature as a framework.

Conclusion

Now the big question is — why spend all of the time and effort required to learn a brand new UI framework when UIKit and AppKit already has everything that’s needed to build a great app?

For me, besides the excitement to learn new technologies and the fact that SwiftUI has made me a lot more productive when building many different kinds of UIs, it all comes down to future-proofing.

If it’s one thing I’ve learned from a decade of app development for Apple’s platforms, it’s that the more you align your projects with Apple’s vision for where their platforms are headed, the better. UIKit and AppKit might not be going away anytime soon, but WidgetKit is likely not going to be the last SwiftUI-only framework that Apple will ever release.

SwiftUI might still have ways to go in order to become the ultimate UI development framework, and it still has rough edges here and there, but — at least in my opinion — it’s definitely worth learning, even if you’ll just end up using it to build tools and prototypes for now.

Got questions, comments or feedback? Feel free to send me an email or find me on Twitter @johnsundell.

Thanks for reading! 🚀