Daily coverage of WWDC20.
A Swift by Sundell spin-off.

How SwiftUI can now be used to build entire iOS apps

Published at 08:45 GMT, 23 Jun 2020
Written by: John Sundell

Up until this year’s release, apps built using SwiftUI have still needed to use mechanisms from Apple’s previous UI frameworks, UIKit and AppKit, to implement their entry points — such as using UIApplicationDelegate to define an app delegate for an iPhone or iPad app.

This year, however, entire apps can now be defined directly using SwiftUI, thanks to a few new additions to its API. For example, let’s say that we’ve been building a podcast app, which uses a TabView as its root view, which then contains three tabs — a library view, a discover view, and a search view:

struct RootView: View {
    var body: some View {
        TabView {
            LibraryView()
            DiscoverView()
            SearchView()
        }
    }
}

On iOS 13 and its sibling OSs from last year, we then had to use a UIHostingController (or NSHostingController on the Mac) to actually render the above view, for example by assigning it as the rootViewController of a UIWindow. But now, the above root view hierarchy can simply be embedded in a type conforming to the new App protocol, and by annotating that type with Swift’s new @main attribute, it’ll act as the entry point for our app — without the need for any app delegate or any other bootstrapping code:

@main struct PodcastApp: App {
    var body: some Scene {
        WindowGroup {
            TabView {
                LibraryView()
                DiscoverView()
                SearchView()
            }
        }
    }
}

The above WindowGroup type is a built-in implementation of another new protocol, Scene, which is a native SwiftUI equivalent to the UIScene API that was introduced last year — primarily in order for iPad apps to gain multi-window support.

While we can also create our own custom Scene types, if we’re looking to always render the same view hierarchy within all of our app’s scenes, then simply using WindowGroup is a great option.

But the cool thing is that, since SwiftUI is so composable, even if we choose to build our own Scene, we can still use WindowGroup to implement its body, while also providing our own custom logic as well.

For example, here we’ve built a custom scene for our podcast app, which uses the new scenePhase environment value to observe whenever the overall phase of our scene changed — for example to detect when it was moved from an active to inactive state:

struct PodcastScene: Scene {
    @Environment(\.scenePhase) private var phase

    var body: some Scene {
        WindowGroup {
            TabView {
                LibraryView()
                DiscoverView()
                SearchView()
            }
        }
        .onChange(of: phase) { newPhase in
            switch newPhase {
            case .active:
                // App became active
            case .inactive:
                // App became inactive
            case .background:
                // App is running in the background
            @unknown default:
                // Fallback for future cases
            }
        }
    }
}

While the new App and Scene protocols currently don’t offer the same amount of functionality and flexibility as their UIKit and AppKit equivalents, the fact that some apps will now be able to use a 100% SwiftUI-based implementation is incredibly cool — and a big step forward for SwiftUI as a framework.

We’ll take a much closer look at what’s new in SwiftUI, as well as how the above new way of defining apps also enables document-based Mac apps to be created in an astonishingly lightweight way, over the next few days.

Thanks for reading! 🚀

Written by: John Sundell