State restoration with SceneStorage in SwiftUI apps

Since iOS 14 we have a property wrapper type that reads and writes to a persisted, per-scene storage SceneStorage. We can use this property similar to how we use @State in our SwiftUI views.

Let's implement state restoration in a tab-based app that stores records of different types of trips.

We define a TabView with three tabs and pass it the binding to our @SceneStorage property called selectedTab. By default it will select the first tab, but when user switches, @SceneStorage property wrapper will make sure that user's selection is persisted.

struct ContentView: View {
    @SceneStorage("selectedTab") var selectedTab: Tab = .car
    
    var body: some View {
        TabView(selection: $selectedTab) {
            CarTrips()
                .tabItem {
                    Image(systemName: "car")
                    Text("Car Trips")
                }.tag(Tab.car)
            TramTrips()
                .tabItem {
                    Image(systemName: "tram.fill")
                    Text("Tram Trips")
                }.tag(Tab.tram)
            AirplaneTrips()
                .tabItem {
                    Image(systemName: "airplane")
                    Text("Airplane Trips")
                }.tag(Tab.airplaine)
        }
    }
    
    enum Tab: String {
        case car
        case tram
        case airplaine
    }
}

It's nice to use enums for tab tags, but they have to be RawRepresentable to save them in @SceneStorage.

Integrating SwiftUI into UIKit Apps by Natalia Panferova book coverIntegrating SwiftUI into UIKit Apps by Natalia Panferova book cover

Check out our book!

Integrating SwiftUI into UIKit Apps

Integrating SwiftUI intoUIKit Apps

UPDATED FOR iOS 17!

A detailed guide on gradually adopting SwiftUI in UIKit projects.

  • Discover various ways to add SwiftUI views to existing UIKit projects
  • Use Xcode previews when designing and building UI
  • Update your UIKit apps with new features such as Swift Charts and Lock Screen widgets
  • Migrate larger parts of your apps to SwiftUI while reusing views and controllers built in UIKit

We can even implement state restoration per subview. For example, our airplane trips will have a toggle to switch between domestic and international trips. All we need to do to persist the latest user selection in this subview is to pass the binding of @SceneStorage property named selectedAirplaneSubview to the Picker view.

struct AirplaneTrips: View {
    @SceneStorage("selectedAirplaneSubview")
    var selectedAirplaneSubview: Subview = .domestic
    
    let subviews = Subview.allCases
    
    var body: some View {
        NavigationView {
            List {
                switch selectedAirplaneSubview {
                case Subview.domestic:
                    Text("Auckland 14.04.2020")
                    Text("Wellington 10.05.2020")
                case Subview.international:
                    Text("Sydney 17.04.2020")
                    Text("Singapore 12.05.2020")
                }
            }
            .navigationBarItems(
                trailing:
                        Picker(
                            "Airplane Trips",
                            selection: $selectedAirplaneSubview
                        ) {
                            ForEach(self.subviews, id: \.self) { subview in
                                Text(subview.rawValue.capitalized)
                            }
                        }
                        .labelsHidden()
                        .pickerStyle(SegmentedPickerStyle())
                        .frame(width: 250)

            )
            .navigationTitle("Airplane Trips")
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
    
    enum Subview: String, CaseIterable {
        case domestic
        case international
    }
}

You can get the full code for this article from our GitHub and test it yourself. Just create a SwiftUI app and replace the ContentView file contents with the code from GitHub.

The selected tab view and the selected airplane view subview will now be persisted and restored when the user comes back to the app. When testing your state restoration make sure you first suspend your app using the Home button, and then stop the debugger in Xcode. You can read about it in Restoring Your App’s State post in "Test State Restoration on a Device" section.