Using SceneStorage for State Restoration in SwiftUI Apps


In iOS 14 beta there is a new property wrapper type that reads and writes to 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 3 tabs and pass it the binding to our @SceneStorage property called selectedTab. By default it will select the first tab, but when the user switches, the @SceneStorage property wrapper will make sure the 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.



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 the @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 example from our GitHub and test it yourself. Just create a SwiftUI app with the new Xcode 12 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 here in Test State Restoration on a Device section.