How to use default launch behavior
This new scene modifier can simplify our apps and reduce the need for Scene Phase.
Overview
When working with multiple scenes (windows, volumes, spaces) in visionOS we often run into an issue. Say the app has a “main” window that can open a secondary window. The user launches the app, uses the main window for a while, then taps a button to open the secondary window. Maybe this is a utility or control panel, or a media viewer. The user closes the main window and when they are done with their task they close the secondary window too. So far so good. But the next time the user taps the app icon, the secondary window shows up. Unless this window has a means to reopen the main window, the user is stuck. The only solution they have is to force quit the app and relaunch it.
In visionOS 1 and 2, we could mitigate issues like this by using Scene Phase to keep track of the state of our scenes. It took some understanding and planning, but it was possible to provide a good user experience.
Starting in visionOS 26 we have a new option. Now we can use a scene modifier called defaultLaunchBehavior. Take these two windows as an example.
struct Garden028App: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.defaultSize(width: 500, height: 500)
WindowGroup(id: "UtilityWindow", makeContent: {
UtilityRoot()
})
.defaultLaunchBehavior(.suppressed)
}
}The user opens the main window, then later opens the utility window. They close the main window, then close the utility window. We used .defaultLaunchBehavior(.suppressed) on the utility window, so when the user taps on the app icon, visionOS will fallback to the first viable scene it can find. In this case, we only have one other scene, so the app reopens with the main window showing.
This simple scene modifier can save us so many headaches. No longer do we need to juggle scene phase or keep track which windows are open.
Video Demo
Let’s see the two scenarios in action.
A video comparison of app launch issues with and without defaultLaunchBehavior
Example Code
struct Garden028App: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.defaultSize(width: 500, height: 500)
WindowGroup(id: "UtilityWindow", makeContent: {
UtilityRoot()
})
.restorationBehavior(.disabled)
.defaultLaunchBehavior(.suppressed)
.defaultSize(CGSize(width: 300, height: 200))
.defaultWindowPlacement { _, context in
if let mainWindow = context.windows.first {
return WindowPlacement(.trailing(mainWindow))
}
return WindowPlacement(.none)
}
ImmersiveSpace(id: "ImmersiveSpace") {
ImmersiveSpaceRoot()
}
// seems to have no effect on immersive spaces
// .defaultLaunchBehavior(.suppressed)
}
}Sample code for this post is available in Garden28 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