Hide a window when presenting an immersive space

Sometimes we want to keep an existing window open, but hide it while presenting an immersive space.

Note: This is a bit of a hack and is not suitable for all situations. It takes a bit of setup to get working.

The Problem

Have you ever wanted to hide a window when showing an immersive space? One option is to close the window and reopen it. But that has some issues.

  • Any manual changes the user made to the window will be reverted. For example, if they resized and move the window. The newly reopened window will be placed directly in front of them with its default size.
  • If the window content contains a complex navigation stack, the user will be all the way back at the root.
  • Unsaved work or edits in that window may be lost if you abruptly close it.

The Setup

For this approach to work we have to customize the window a bit. This will only work if we use the .plain window style.

.windowStyle(.plain)

That can cause its own issues though. Now our window has no background. We can re-add the glass background to our windows root view.

.glassBackgroundEffect()

Next, we need to fix resizing. The root view with the glass background won’t resize correctly unless we switch windowResizability to .contentSize.

.windowResizability(.contentSize)

We’ll also need to provide a frame with min and max size.

.frame(minWidth: 300, idealWidth: 600, maxWidth: 1200, minHeight: 300, idealHeight: 600, maxHeight: 800)

I like to provide a default size as well.

.defaultSize(CGSize(width: 600, height: 600))

You can learn about all these concepts with the Windows series on the Learn visionOS page.

The Solution

With the setup out of the way, the solution is actually very simple. Our app model maintains some state that indicates if the immersive space is showing. When this value is true, we can hide the content of our window.

// When the immersive space is showing, hide the content.
.opacity(appModel.gardenOpen ? 0 : 1)

We can also use .persistentSystemOverlays to hide the window close button, resizing controls, and drag bar.

// When the immersive space is showing, hide the window controls
.persistentSystemOverlays(appModel.gardenOpen ? .hidden : .visible)

If your window contains toolbars or ornaments, you will need to hide those as well.

If this hack isn’t to your liking, don’t worry. We’ll be back with another option soon.

See also:
Using Push Window to hide the main app window in an immersive space
Hide a window with an invisible Push Window while presenting an immersive space

Video Demo

A video demo of this example in Apple Vision Pro. A window presents an option to enter an immersive space. When the space is active, the window is hidden. Leaving the space restores the window in the original location.

Example Code

The main work for this example is done in the app file. You can find this and the rest of the code in Garden 025 in this repo.

struct Garden025App: App {

    @State private var appModel = AppModel()

    var body: some Scene {
        WindowGroup(id: "MainWindow") {
            ContentView()
                .environment(appModel)
            // Setup 2: Manually create a resizable window with a glass background
                .frame(
                    minWidth: 300,
                    idealWidth: 600,
                    maxWidth: 1200,
                    minHeight: 300,
                    idealHeight: 600,
                    maxHeight: 800
                )
                .glassBackgroundEffect()
            // Focus: When the immersive space is showing, hide the content.
                .opacity(appModel.gardenOpen ? 0 : 1)
            // Focus: We can also hide the window drag bar and controls
                .persistentSystemOverlays(appModel.gardenOpen ? .hidden : .visible)
        }
        // Setup 1: The window must use .plane style to hide the glass background when we hide the other window content
        .windowResizability(.contentSize)
        .defaultSize(CGSize(width: 600, height: 600))
        .windowStyle(.plain)

        ImmersiveSpace(id: "GardenScene") {
            ImmersiveView()
                .environment(appModel)

        }
        .immersionStyle(selection: .constant(.full), in: .full)    }
}

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?