How to align a newly opened volumetric window with the center of an existing 2D window in visionOS?

I’m building a visionOS app that starts with a regular 2D SwiftUI window. From that 2D window, the user can enter a volumetric mode, where I want to open a large volumetric WindowGroup and have it appear centered around the same spatial position as the original 2D window.

The volumetric window is physically large, roughly over 1m × 1m × 30cm. Because of that, placement behavior is very noticeable.

My intended behavior is:

  1. User is interacting with a regular 2D window.
  2. User taps a button.
  3. A large volumetric window opens.
  4. The volumetric window appears in front of the user, ideally centered on or near the original 2D window’s position.
  5. The original 2D window is dismissed or replaced.

My current workaround is to call openWindow(id:) for the volumetric window, then dismiss the original 2D window. This works in the sense that the volume is created, but its initial position is noticeably offset from the original 2D window.

I also tried using defaultWindowPlacement to control the placement of the volumetric window relative to the existing 2D window. I tested placements such as .below, .trailing, and other relative positions.

However, because the volumetric window is large, the result is worse: when I open the volume from the 2D window, the volumetric window appears to move instantly far away from the user’s view, almost as if it flies out of the visible workspace. After that, I can no longer see or interact with the volume.

Interestingly, if I then go back to the system Home View and tap the app icon again, the volumetric window appears normally in front of the user.

Here is a simplified version of my setup:

@main struct MyApp: App { var body: some Scene { WindowGroup(id: "main") { MainWindowView() } WindowGroup(id: "volume") { VolumeView() } .windowStyle(.volumetric) .defaultSize(width: 1.0, height: 1.0, depth: 0.3, in: .meters) // I also tried defaultWindowPlacement here, // using placements such as .below, .trailing, etc. } } struct MainWindowView: View { @Environment(.openWindow) private var openWindow @Environment(.dismissWindow) private var dismissWindow var body: some View { Button("Open Volume") { openWindow(id: "volume") dismissWindow(id: "main") } } }

What I would like to know:

  1. Is there a supported way to open a large volumetric window from a 2D window while preserving or approximating the 2D window’s spatial center?
  2. Is defaultWindowPlacement expected to work reliably for large volumetric windows, or can relative placements such as .below or .trailing cause the volume to be placed outside the user’s comfortable visible area?
  3. Is there any API that exposes the current 2D window’s spatial position or center so I can place the volumetric window more precisely?
  4. Can pushWindow(id:) be used to replace a 2D window with a volumetric window while preserving placement, or is this transition not currently supported?
  5. Why would the same volumetric window appear far away when opened from the 2D window, but appear normally in front of the user when the app is reopened from the system Home View?
  6. What is the recommended UX or technical pattern for transitioning from a regular 2D window into a large volumetric window without the volume jumping or appearing outside the user’s view?

I’m testing this on:

  • visionOS version: [26.5]
  • Xcode version: [26.4.1]
  • Device or Simulator: [vision pro m2 & m5]
  • SwiftUI app lifecycle
  • Source scene: regular WindowGroup
  • Destination scene: WindowGroup with .windowStyle(.volumetric)
  • Approximate volume size: over 1m × 1m × 30cm

Any guidance on the recommended placement strategy for large volumetric windows would be appreciated.

Answered by Vision Pro Engineer in 889265022

Hello @Sakuya_Izayoi , thank you for your question!

In general, developers have limited control over where the system places the first window for your app. The system will attempt to place the first window in front of the person wearing the device, although you can place other windows relative to existing open windows, as you have discovered with the defaultWindowPlacement(_:) API.

To answer your questions directly:

  1. No, there is no public API to center a volumetric window on an existing window. Window placement on visionOS works semantically using APIs like .trailing(window), .utilityPanel, etc.

  2. defaultWindowPlacement(_:) will align your volume to the edge of the reference window. If your reference window is one meter wide, and your volume configured with .trailing(window) is one meter wide, the system will place the volume 1 meter to your right when it opens.

  3. No. The system and the user control window placement, so you will need to use defaultWindowPlacement(_:)

  4. No, pushWindow requires that both views be the same window style.

  5. The system will attempt to place the first window in an app session in front of the user. When your volume is the first window, the system will ignore defaultWindowPlacement and place the window in front of the user. When you open your volume from another window in your app (so your volume is no longer the first window), then the system will respect defaultWindowPlacement and position the volume relative to the reference window.

  6. You have a couple of options. .utilityPanel may get you close to what you want: it will place the volume relative to the person using your app, somewhere they can easily reach. Alternatively, you may want to reconsider the UI flow: open the volume first, and spawn the 2D window inside the volume. This way, you will have direct control over the position of the 2D window inside the volume. If your volume has content, you can simply hide the content while the 2D window is active.

You can also send us feedback using Feedback Assistant, and I strongly encourage you to do so if you feel like there is still functionality that you are missing.

Accepted Answer

Hello @Sakuya_Izayoi , thank you for your question!

In general, developers have limited control over where the system places the first window for your app. The system will attempt to place the first window in front of the person wearing the device, although you can place other windows relative to existing open windows, as you have discovered with the defaultWindowPlacement(_:) API.

To answer your questions directly:

  1. No, there is no public API to center a volumetric window on an existing window. Window placement on visionOS works semantically using APIs like .trailing(window), .utilityPanel, etc.

  2. defaultWindowPlacement(_:) will align your volume to the edge of the reference window. If your reference window is one meter wide, and your volume configured with .trailing(window) is one meter wide, the system will place the volume 1 meter to your right when it opens.

  3. No. The system and the user control window placement, so you will need to use defaultWindowPlacement(_:)

  4. No, pushWindow requires that both views be the same window style.

  5. The system will attempt to place the first window in an app session in front of the user. When your volume is the first window, the system will ignore defaultWindowPlacement and place the window in front of the user. When you open your volume from another window in your app (so your volume is no longer the first window), then the system will respect defaultWindowPlacement and position the volume relative to the reference window.

  6. You have a couple of options. .utilityPanel may get you close to what you want: it will place the volume relative to the person using your app, somewhere they can easily reach. Alternatively, you may want to reconsider the UI flow: open the volume first, and spawn the 2D window inside the volume. This way, you will have direct control over the position of the 2D window inside the volume. If your volume has content, you can simply hide the content while the 2D window is active.

You can also send us feedback using Feedback Assistant, and I strongly encourage you to do so if you feel like there is still functionality that you are missing.

Thank you for the detailed explanation. After doing more testing, I think I found that the main issue in my case is not only the initial placement offset, but the relationship created between the volumetric window and the reference 2D window when using defaultWindowPlacement(_:).

When I open the volumetric window using defaultWindowPlacement(_:) relative to an existing 2D window, the volume appears to have an initial binding to that reference window:

  • If the user moves the reference 2D window, the volumetric window moves together with it.
  • If the user closes the reference 2D window, the volumetric window is also closed.
  • If the user manually moves the volumetric window once, this binding appears to be permanently broken for that window session. After that, moving or closing the original reference window no longer affects the volume.

This behavior does not happen when I open the same volumetric window without using defaultWindowPlacement(_:).

So my follow-up question is: is this reference-window binding an intentional part of the defaultWindowPlacement(_:) design on visionOS? In other words, when a window is placed relative to another window, should developers expect the new window to remain associated with the reference window until the user manually repositions it?

For my app, I eventually chose a different transition approach. Instead of using defaultWindowPlacement(_:) to place the volume directly relative to the original 2D main menu window, I now use a temporary loading utility window as an intermediate step.

The flow is:

  1. From the 2D main menu window, open a temporary loading window using defaultWindowPlacement(_:) with the utility panel placement.
  2. After the loading utility window appears, dismiss the original 2D main menu window.
  3. Open the volumetric window without using relative defaultWindowPlacement(_:), allowing the system to place it automatically.
  4. Once the volumetric window is ready, dismiss the temporary loading utility window.

In practice, this gives me a much better result. The temporary loading utility keeps the transition visually understandable, while the volumetric window is placed by the system in a position that feels closest to the original 2D window’s location from the user’s perspective. It also avoids the reference-window binding issue between the original main menu window and the volume.

The main reason I moved away from using defaultWindowPlacement(_:) directly for the volumetric window is that it does not provide the one behavior I need most: replacing or transitioning from a 2D window to a volumetric window while preserving the approximate spatial center. The available semantic placements such as trailing, below, or utility panel are useful for secondary windows, but they do not work well for this particular 2D-to-volume transition.

I’m no longer blocked, but I wanted to share this observation and confirm whether the binding behavior is expected. If this is not the intended behavior, I can file a Feedback Assistant report with a minimal sample project and screen recording.

Hey @Sakuya_Izayoi,

It's not expected that there is a binding to the initial window. The defaultWindowPlacement(_:) API is only meant to control the initial placement of the window. After the window is placed, either window should be able to be closed or moved independently from the other. I'm unsure if there's bug in your app or if a resolution may involve changes to Apple's software. I'd greatly appreciate it if you could open a bug report, include a video of the issue and a sample project, and post the FB number here once you do.

Additionally, I'd love to have the team take a look at the experience you've made with the temporary loading utility and see if there are ways that we could improve the APIs to handle this case more gracefully. It feels like there could be an opportunity to improve the defaultWindowPlacement(_:) API to account for this case. For us to consider adding the necessary functionality for this behavior, please file an enhancement request using Feedback Assistant. If you could include a video of both cases using defaultWindowPlacement(_:) and using the temporary loading utility and a sample code project for each that will help us fully understand the desired behavior.

Bug Reporting: How and Why? has tips on creating your bug report and enhancement request.

Thanks!
Michael

How to align a newly opened volumetric window with the center of an existing 2D window in visionOS?
 
 
Q