Automatic State Restoration with SceneStorage

DevTechie Inc
May 11, 2023

SceneStorage property wrapper was introduced in iOS 14 and is the type that reads and writes to persist data per-scene.

We can use SceneStorage property wrapper when we need to save user’s input and to automatically restore the value between scene launches. In other words, SceneStorage provides automated state restoration of values for scene.

SceneStorage is used to store small amounts of data within the scope of app scene instances and is ideal for saving and restoring the state of a scene between app launches. SceneStorage is best for lightweight data storage only and large size data should not be stored in SceneStorage otherwise it will lead to poor app performance.

Sensitive data should not be stored in SceneStorage.

SceneStorage works very similar to State, except that its initial value is restored by the system if it was previously saved, and the value is shared with other SceneStorage variables in the same scene.

Saving and restoring values from SceneStorage comes out of the box and system takes the responsibility to perform those actions. The underlying data that backs SceneStorage is not available for us to access, so we must access the data using the SceneStorage property wrapper. There is no way to know when or how often data is persisted. Each Scene has its own notion of SceneStorage, so data is not shared between scenes.

Note If the Scene is explicitly destroyed (e.g. the switcher snapshot is destroyed on iPadOS or the window is closed on macOS), the data is also destroyed.

Imagine a use case where a user is filling out a form and the process is interrupted by a phone call, system message or something else with higher priority which places the app into the background. If user forgets to come back to the app there is a possibility of loosing partially filled information in the form due to app termination or device restart. This is where SceneStorage can come handy and we can avoid data loose by using SceneStorage property wrapper.

For our example, we will put together a form where user can enter their name, email and address information.

struct DevTechieSceneStorageExample: View {
    @State var name = ""
    @State var email = ""
    @State var address = ""
    @State var city = ""
    @State var state = ""
    
    var body: some View {
        NavigationStack {
            Form {
                Section(header: Text("Personal Information")) {
                    TextField("Enter name", text: $name)
                    TextField("Enter email", text: $email)
                }
                
                Section(header: Text("Address Information")) {
                    TextField("Enter address", text: $address)
                    TextField("Enter city", text: $city)
                    TextField("Enter state", text: $state)
                }
            }
            .navigationTitle("DevTechie")
        }
    }
}

Build and run.

Notice that we filled half of the info and then app was closed, upon relaunching, we will have to start over again.

This can be avoided by using SceneStorage and it can be done as easily as replacing the State property wrapper with SceneStorage property wrapper.

struct DevTechieSceneStorageExample: View {
    @SceneStorage("name") var name = ""
    @SceneStorage("email")  var email = ""
    @SceneStorage("address")  var address = ""
    @SceneStorage("city") var city = ""
    @SceneStorage("state") var state = ""
    
    var body: some View {
        NavigationStack {
            Form {
                Section(header: Text("Personal Information")) {
                    TextField("Enter name", text: $name)
                    TextField("Enter email", text: $email)
                }
                
                Section(header: Text("Address Information")) {
                    TextField("Enter address", text: $address)
                    TextField("Enter city", text: $city)
                    TextField("Enter state", text: $state)
                }
            }
            .navigationTitle("DevTechie")
        }
    }
}

Build and run again.