How to use scene restoration with windows and volumes

Exploring scene restoration in visionOS.

Overview

Starting in visionOS 26 users can lock windows in place. There are two ways to do this.

  1. Snapping a window or volume to a surface will also lock it
  2. They can use the context menu on the window controls to lock a free-floating scene in place

These locked windows will be restored by visionOS. From the docs:

scenes will restore themselves depending on the default behavior for the platform

I haven’t found any documentation that describes the exact behavior for visionOS (feedback FB18331741), but we can guess. Scenes are restored:

  • When reentering a room
  • Putting on the device
  • After restarting the device

As of visionOS 26 beta 2, force quitting an app will remove any locked scenes.

So what if we have a scene that should not be restored? For example, in Project Graveyard the main scene of the app is a volume. I also have a small utility window that serves as an editor for the content of the volume. I’d like to let users lock the volume, but the utility window is transient. It should not be locked or restored. That is where the restorationBehavior scene modifier comes in.

WindowGroup(id: "UtilityWindow", makeContent: {
  UtilityRoot()
})
.restorationBehavior(.disabled)

When set to disabled the utility window can no longer be locked. Users can still snap this window to a surface, but it won’t be locked in place or restored later.

When to use this? Apple suggested leaving as much control to users as possible. But for temporary or transient windows it may be best to disable scene restoration. Launch screens, picker windows, utility windows etc. may be best opted out of restoration.

Caveats and Gotchas

There are a few things to keep in mind when working with scene restoration

  • visionOS does not restore immersive spaces, so these concepts only apply to windows and volumes.
  • Locking and restoration does not work in the Simulator. Make sure to test your apps on device.
  • Locking and restoration does not work when you are running an app from Xcode. Build and run your app, then quit it from Xcode. Launch the app on device from the Home Screen and you should be able to test these features normally.

Video Demo

A video demo showing scene restoration and snapping in action

Example Code

This example app has two scenes. One that can be locked and one that can’t.

import SwiftUI

@main
struct Garden028App: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .defaultSize(width: 500, height: 500)

        WindowGroup(id: "UtilityWindow", makeContent: {
            UtilityRoot()
        })
        .restorationBehavior(.disabled)
        .defaultSize(CGSize(width: 300, height: 200))
        .defaultWindowPlacement { _, context in
            if let mainWindow = context.windows.first {
                return WindowPlacement(.trailing(mainWindow))
            }
            return WindowPlacement(.none)
        }

    }
}

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.

Questions or feedback?