State Restoration in SwiftUI

Learn how to use SceneStorage in SwiftUI to restore iOS app state. By Tom Elliott.

Leave a rating/review
Download materials
Save for later
Share

In almost any app imaginable, some state will be defined by actions the user has taken — the tab the user last selected, the items the user added to a basket or the contents of a draft message.

It would be confusing for users if they were to stop using your app for a short while, then come back to find the state lost. But this is the default behavior for iOS apps. Why? Because after a user puts an app into the background, the operating system may choose to terminate it at any time. When this happens, the system discards in-memory state.

There is a feature in iOS where the operating system can restore state when an app is re-launched. This is known as state restoration.

In this tutorial, you’ll learn:

  • How to add state restoration to your SwiftUI apps.
  • The @SceneStorage property wrapper for saving state of simple data.
  • Using NSUserActivity to pass state when launching an app.

Time to get started!

Getting Started

Download the project materials by clicking the Download Materials button at the top or bottom of this tutorial. The materials contain a project called Hawk Notes. This tutorial builds an app for making study notes for the Shadow Skye series, a trilogy of epic fantasy books.

The demo app - Hawk Notes

Open the starter project. Now, build and run the app using Product ▸ Run from the toolbar or by clicking the Run arrow at the top of the Project Navigator. Once running, the app displays four tabs: one for each of the three books and a fourth for your favorite characters.

Within each of the first three tabs, you'll see four things:

  • An overview of the book.
  • A link to view it on Amazon.
  • A list of study notes you can make about the book, which is currently empty.
  • A list of the book's main characters.

Tap a character, and the app navigates to a character detail screen. This screen contains a synopsis for the character as well as a list of notes you can add about that character. You can also tap the heart to mark this character as one of your favorites.

Finally, tap the Favorites tab. There, the app lists all the characters, split into two sections: one for your favorites and another for all the others.

Switch back to Xcode and take a look around the code. Open ContentView.swift. This is the entry point into the app proper. Notice how it defines a BookModel environment object. This model contains the information for each book and is the primary data source for the app. The content view itself displays a tab view with the four tabs from above — one for each book plus the favorites tab.

Next, open BookView.swift. This is the view for displaying a book. The view comprises a vertical stack containing an overview, a link to view the book on Amazon, a list of notes and finally, a list of characters for this book.

Next, open CharacterView.swift. Here, a ScrollView contains a VStack showing views for the character's avatar, a toggle switch for marking the character as a favorite, a synopsis for the character and finally, the notes for the character.

Finally, open FavoritesView.swift. This view shows a list of all the main characters for the three books split into two sections: first, a list of your favorite characters, and secondly, a list of all the other characters.

Switch to the Simulator and select the third tab for The Burning Swift. Now, put the app in the background by selecting Device ▸ Home. Next, switch back to Xcode and stop the app from running by selecting Product ▸ Stop in the menu. Build and run the app again.

Note: You'll perform the process of putting the app in the background before terminating it many times throughout the rest of this tutorial. From this point on, if the tutorial asks you to perform a cold launch of the app, this is what you should do.

Once the app restarts, note how the third tab is no longer selected. This is an example of an app that doesn't restore state.

Example of an app without state restoration

It's time to learn a little more about how a SwiftUI app's Scene works now.

Understanding Scene Storage

In SwiftUI, a Scene is a container for views that have their lifecycle managed by the operating system. All iOS apps start with a single Scene. Open AppMain.swift, and you can see this for yourself.

// 1
@main
struct AppMain: App {
  private var booksModel = BooksModel()

  // 2
  var body: some Scene {
    WindowGroup("Hawk Notes", id: "hawk.notes") {
      ContentView()
        .environmentObject(booksModel)
    }
  }
}

In the code above, which is already in your app:

  1. AppMain is of type App. The @main attribute signals to the runtime that this is the entry point for the entire app.
  2. The body property for an App returns a Scene that acts as the container for all views in the app.

To make state restoration really easy, Apple provides a new attribute you can add to a property: @SceneStorage.

SceneStorage is a property wrapper that works very similarly to the State property wrapper, which you may have used already. Like State, your code can both read and write to a property attributed with @SceneStorage, and SwiftUI automatically updates any parts of your app that read from it.

SwiftUI also saves the value of properties attributed with @SceneStorage into persistent storage — a database — when the app is sent to the background. Then, it automatically retrieves and initializes the property with that value when the app enters the foreground again.

Because of this, SceneStorage is perfect for adding state restoration to your apps.

It really is that simple! So let's now start coding.

Saving State

It's time to add some state restoration goodness to the Hawk Notes app. Open ContentView.swift.

Near the top of the view, find the line that defines the selected tab for the app:

@State var selectedTab = ""

Update this line to use the SceneStorage property wrapper like so:

@SceneStorage("ContentView.CurrentTab") var selectedTab = ""

With this change, you've updated the selectedTab property to use SceneStorage — rather than State — with an identifier to use as its storage key: ContentView.CurrentTab. The identifier should be unique within your app. This allows you to create multiple SceneStorage variables which won't clash with each other.

Build and run the app. Once running, switch to the third tab again. Then perform a cold launch of the app that you learned how to perform earlier.

Restoring the selected tab

How easy was that! By simply changing the attribute on the selectedTab property from @State to @SceneStorage(...), your app now automatically restores the state correctly when launched. That was easy!

State restoration in action