Write a SwiftUI only app

This article is about some news in SwiftUI app cycle.
With the second release of SwiftUI, is finally possible to make an app entirely in SwiftUI, without having to embed it in UIKit or AppKit on the Mac. We’ll see how to create a SwiftUI only app, manage independent Scenes and share data between them.
As usual, there is a repository on GitHub where you can find all the code pasted here.

@main

The @main attribute is one of the new additions in Swift 5.3 (you can see the proposal here) and allows the declaration of the entry point of a program. We can still use a main.swift file, but the @main attribute allow us to create a struct or a class with a main function and use it as the entry point. SwiftUI uses the @main attribute together with the new App struct to provide the new entry point


@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
...

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
...
// Create the SwiftUI view that provides the window contents.
        let contentView = ContentView()

        // Use a UIHostingController as window root view controller.
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: contentView)
            self.window = window
            window.makeKeyAndVisible()
        }
}

we had @UIApplicationMain to mark the Application delegate and we needed to initiate both the AppDelegate and the SceneDelegate, then create the first SwiftUI view and embed it in a UIHostingController.

With SwiftUI 2, as you saw in the first code snippet you have to declare an App struct, and its body property is of type Scene.
The main view, instead of being embedded in the UIHostingController is now part of a WindowGroup.
A WindowGroup is a container for a view hierarchy, used by each window of an application. While you only have one instance of your app on iOS and watchOS, on iPadOS and macOS you can have multiple windows. Each window has an independent state, so every instance has its @State independent variables.

SceneStorage

We saw that your app can have multiple scenes with separate states, but what if we want to save those states? A particular scene of your app may be terminated by the operating system, and the good news is you can restore its state as the scene is re opened, by using the new property wrapper SceneStorage.
If you want to find out more about property wrappers, this is my article about them.
Let’s start with an example, we have a simple view with a Button to switch between bold text and normal text.


struct ContentViewMultiple: View {
    @Binding var counter:Int
    @SceneStorage("bold") var showBold:Bool = false
    //@State var showBold = false
    
    var body: some View {
        if showBold == true {
            Text("Counter = \(counter)").bold()
        }
        else {
            Text("Counter = \(counter)")
        }
        Button {
            counter = counter + 1
        } label: {
            Text("Increase")
        }
        Button {
            showBold.toggle()
        } label: {
            Text("Switch")
        }
    }
}

In order to switch between bold and normal we’d need a @State variable with a boolean. This variable is persistent as long as the view is on screen, but we want to have it persistent when we restore a Scene. All we need to to is replace @State with @SceneStorage. The latter takes a parameter, think about a key of a dictionary so you can store multiple variables with different names. That’s it, try to close and open the app again and for each Scene, you’ll see bold or normal text depending on what you selected.

AppStorage

SceneStorage is useful to save Scene state, but if a Scene is destroyed (app is force killed by the user) you lose everything you had in SceneStorage.
What if you want to have a persistent storage for you app? AppStorage is the answer.
You can use it in a similar way, by specifying a key as a parameter, and AppStorage will save your stuff in UserDefaults.


@main
struct SwiftUI2App: App {
    @AppStorage("counter") var counter = 0
    
    var body: some Scene {
        WindowGroup {
            ContentViewMultiple(counter:$counter)
        }
    }
}

I put it on the App file as an example, but it would work on the View where I put the SceneStorage as well.

That’s all. I recommend watching this WWDC video about the topic that goes into details about the new App protocol.
Happy coding 🙂