How it works
ScenePhase is an enumeration that describes the operational state of a scene or app — whether it is running and visible, momentarily interrupted, or no longer onscreen. SwiftUI publishes the current phase through the environment, so your views and scenes can observe lifecycle transitions declaratively instead of relying on app- or scene-delegate callbacks. Read it when you need to pause work, persist state, or release resources as the system foregrounds and backgrounds your app. Its cases — active, inactive, and background — give you a single, platform-agnostic signal for these moments.
Read the phase from the environment with @Environment(\.scenePhase)
SwiftUI keeps the current ScenePhase in the environment under the scenePhase key value. Declare an environment property to pull it into a view, and SwiftUI updates that property — and re-evaluates the body — whenever the phase changes. Here
@Environment(\.scenePhase) private var scenePhasemakes the live phase available throughoutbody.Switch over the cases: active, inactive, and background
ScenePhase has three cases.
activemeans the scene is in the foreground and interactive;inactivemeans it's visible but not receiving events (for example, mid-transition or partially obscured); andbackgroundmeans it's no longer onscreen. Because it's an enum, you pattern-match it directly — the example routes each case to a label in aswitch newPhaseblock.Handle @unknown default for forward compatibility
ScenePhase can gain cases in future SDKs, so an exhaustive switch should include an
@unknown defaultclause. This lets the compiler warn you when new cases appear while still compiling against current and future systems. The example falls through tolastPhase = "unknown"for any case it doesn't recognize.React to transitions with onChange(of:)
ScenePhase conforms to Equatable, so you can feed it to onChange(of:) to run side effects only when the phase actually moves — the natural place to checkpoint state or wind down activity. The example attaches
.onChange(of: scenePhase)and uses the deliverednewPhaseto update what it displays; you'd put save-on-background logic in the.backgroundbranch.
print(newPhase) call inside the .onChange(of: scenePhase) closure, then background and foreground the app to watch it report inactive on the way to background and back through to active.Example & preview
Press Run live & edit to compile it in your browser — then edit the Swift on the left and the preview re-renders live.
struct ScenePhaseDemo: View {
@Environment(\.scenePhase) private var scenePhase
@State private var lastPhase = "active"
var body: some View {
VStack(spacing: 12) {
Text("Scene Phase")
.font(.headline)
Text(lastPhase)
.font(.title2.bold())
.foregroundStyle(.tint)
Text("Switch apps or lock the screen to see this change.")
.font(.caption)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
}
.padding()
.onChange(of: scenePhase) { _, newPhase in
switch newPhase {
case .active: lastPhase = "active"
case .inactive: lastPhase = "inactive"
case .background: lastPhase = "background"
@unknown default: lastPhase = "unknown"
}
}
}
}