The system can kill a backgrounded app, and on relaunch @State and @StateObject reset to
their initial values, losing where the user was. Two tools restore that context:
@SceneStorage for per-scene UI state and UserActivity for resuming a marked flow.
SceneStorage
@SceneStorage persists a value in storage the system manages per scene; other scenes cannot
read it, and the system restores it on relaunch. It suits tab selection, active navigation
links, and sheet presentation flags.
struct RootView: View {
@SceneStorage("selectedTab")
private var selectedTab = 0
@SceneStorage("bloodPressureFormShown")
private var bloodPressureFormShown = false
var body: some View {
TabView(selection: $selectedTab) {
NavigationView {
SummaryContainerView()
.sheet(isPresented: $bloodPressureFormShown) {
AddBloodPressureView()
}
}
.tabItem { Label("today", systemImage: "heart") }
.tag(0)
NavigationView { SettingsView() }
.tabItem { Label("Settings", systemImage: "gear") }
.tag(1)
}
}
}
Two cautions: the system gives no guarantee about when or how often the data persists, and it is
not a place for sensitive data. @SceneStorage complements @State; it does not replace it.
UserActivity
UserActivity marks a flow with data the system preserves across launches, such as a purchase
flow with a product ID. The userActivity modifier advertises the activity while a condition
holds, and populates its payload.
struct PurchaseView: View {
static let userActivity = "com.aaplab.app.purchase"
let product: Product
@State private var isPurchaseLinkActivated = false
var body: some View {
VStack {
Text(product.title)
NavigationLink(isActive: $isPurchaseLinkActivated) {
CheckoutView(product: product)
} label: {
Label("Go to checkout", systemImage: "creditcard")
}
}
.userActivity(PurchaseView.userActivity, isActive: isPurchaseLinkActivated) { activity in
activity.title = "Purchase \(product.title)"
activity.userInfo = ["id": product.id]
}
}
}
On relaunch, handle the stored activity at the app root with onContinueUserActivity and
navigate back into the flow.
WindowGroup {
RootView()
.onContinueUserActivity(PurchaseView.userActivity) { userActivity in
if let id = userActivity.userInfo?["id"] {
// mutate app state and navigate to the purchase view
}
}
}