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
.
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.