How to use scene phase to track and manage window state
This example demonstrates how to use Scene Phase in visionOS to monitor and control window state, with practical code for managing multiple windows effectively.
Update: visionOS 26 gave us a new option to solve some common issues with multi-window apps. See How to use default launch behavior for more information.
According to the documentation, Scene Phase works in two main ways:
- Report the phase / state of the current scene from the context of a given view.
- Report the phase / state of the all scenes for the app.
We’re going to cover the first of these, using Scene Phase determine if a window is open or closed.
Setup
Let’s create two window groups and pass them our app model. This model has a couple of booleans that we’ll use later.
// App Model
var mainWindowOpen: Bool = true
var yellowFlowerOpen: Bool = falseWindowGroup(id: "MainWindow") {
ContentView()
.environment(appModel)
}
.defaultSize(width: 500, height: 500)
.defaultWindowPlacement { content, context in
if let new = context.windows.first(where: { $0.id == "YellowFlower" }) {
return WindowPlacement(.leading(new))
} else {
return WindowPlacement(.none)
}
}
WindowGroup(id: "YellowFlower") {
YellowFlower()
.environment(appModel)
}
.defaultSize(width: 500, height: 500)
.defaultWindowPlacement { content, context in
if let new = context.windows.first(where: { $0.id == "MainWindow" }) {
return WindowPlacement(.trailing(new))
} else {
return WindowPlacement(.none)
}
}We also set up a default placement on these window groups so they will always appear next to each other. Learn more about default placement.
Scene Phase
In the ContentView we can import scenePhase from the environment. Then we can listen for changes to scene phase using onChange(of:initial:_:).
// It's important to set initial to true so we can be sure this fires when the window opens.
.onChange(of: scenePhase, initial: true) {
switch scenePhase {
case .inactive, .background:
appModel.mainWindowOpen = false
case .active:
appModel.mainWindowOpen = true
@unknown default:
appModel.mainWindowOpen = false
}
}In the code above, we switch on the scene phase. We set the open state of our main window in the app model based on the case. The second window is setup in much the same way.
Window Controller
To manage our windows we can set up a simple view called WindowController that shows a list with two buttons. These buttons can toggle our windows and report their open/closed state inline.
struct WindowController: View {
// Get the app model
@Environment(AppModel.self) private var appModel
// Get open and dismiss window
@Environment(\.openWindow) private var openWindow
@Environment(\.dismissWindow) private var dismissWindow
var body: some View {
List {
Button(action: {
// If the main window is open, close it. Else open it.
if appModel.mainWindowOpen {
dismissWindow(id: "MainWindow")
} else {
openWindow(id: "MainWindow")
}
}, label: {
HStack {
Text("Main Window")
Spacer()
Text(appModel.mainWindowOpen ? "Open" : "Closed")
}
})
Button(action: {
// If the second window is open, close it. Else open it.
if appModel.yellowFlowerOpen {
dismissWindow(id: "YellowFlower")
} else {
openWindow(id: "YellowFlower")
}
}, label: {
HStack {
Text("Yellow Flower")
Spacer()
Text(appModel.yellowFlowerOpen ? "Open" : "Closed")
}
})
}
}
}Demo
visionOS simulator video showing scene phase tracking between two windows.
With this simple setup we can do several things:
- Main window
- Open and close the Yellow Flower Window
- Close the main window if another window is open â€
- Flower window
- Open and close the Main Window
- Close the yellow flower window if another window is open
- Side effect: we only ever have a single instance of either window, so we don’t have to add special code to prevent duplicate windows
†Calling
dismissWindowfrom the only window in an app seems to do nothing. My guess is that visionOS configured to keep at least one scene/window open until the user performs an action to close it.
To recap:
- Scene phase is setting the setting a boolean for each window in our app model
- Window controller can check the app model to determine how to render it’s views and which actions to preform
We have just started to scratch the surface with Scene Phase. How are you going to incorporate it into your apps?
Sample code for this post is available in Garden06 in Step Into Examples on GitHub
Support our work so we can continue to bring you new examples and articles.
Download the Xcode project with this and many more examples from Step Into Vision.
Some examples are provided as standalone Xcode projects. You can find those here.

Follow Step Into Vision