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.

Swift Gems by Natalia Panferova book coverSwift Gems by Natalia Panferova book cover

Level up your Swift skills!$35

100+ tips to take your Swift code to the next level

Swift Gemsby Natalia Panferova

  • Advanced Swift techniques for experienced developers bypassing basic tutorials
  • Curated, actionable tips ready for immediate integration into any Swift project
  • Strategies to improve code quality, structure, and performance across all platforms

Level up your Swift skills!

100+ tips to take your Swift code to the next level

Swift Gems by Natalia Panferova book coverSwift Gems by Natalia Panferova book cover

Swift Gems

by Natalia Panferova

$35

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.