Ben Gottlieb
Ben Gottlieb

Reputation: 85542

Single instance windows on visionOS

It appears that Window is not available on visionOS, only WindowGroup. If I want to have a window with only one instance (say, a Menu or similar), how can I ensure that each time I call openWindow, the same window appears, and a new one is not created?

Upvotes: 4

Views: 1050

Answers (2)

laka
laka

Reputation: 796

I have a rather quick and dirty solution that did the job for me. I had no other idea than just caching the windows ID's in some global Set and check against that whenever opening a new window.

E.g. inside of your App conforming struct:

@main
struct MyApp: App {
    static var openendWindowsIDs: Set<String> = []
    //...
}

Then wherever you open that new window you can check against that Set (The following is just an example resulting in some boilerplate so you would probably abstract it away somehow)

Button {
    let windowID = "myWindowsUniqueId"
    if !MyApp.openendWindowsIDs.contains(windowID) {
        openWindow(id: windowID)
    }
    MyApp.openendWindows.insert(windowID)
}

(Or even do not show this button or whatever triggers the window at all by checking before)

Also do not forget to remove the window's id somewhere it makes sense, e.g. in the main view of that window you are closing:

.onDisappear(perform: {
    MyApp.openendWindows.remove("myWindowsUniqueId")
})

What's mentioned in the docs but did not work for me:

The documentation for WindowGroup mentions:

If a window with a binding to the same value that you pass to the openWindow action already appears in the user interface, the system brings the existing window to the front rather than opening a new window. I still always got new instances. That is why I use the example above now.

Together with this code example:

WindowGroup(for: Message.ID.self) { $messageID in
    MessageDetail(messageID: messageID)
}

How I understood this: If you call openWindow with the same (here) messageID passed, there should not be opened a new window. However, I did not get this to work.

Upvotes: 1

svarrall
svarrall

Reputation: 9277

I suspect this may be intentional to allow users to 'find' windows they've misplaced. It looks like it will be easy to lose things in the environment and perhaps it will make sense from a UX perspective to allow the user to have multiple sets of controls around their environment.

If you want to control only opening one instance of a WindowGroup, then dismissing it first, before opening is a reasonable way, as that will bring it back to where the user is looking.

@Environment(\.openWindow) private var openWindow
@Environment(\.dismissWindow) private var dismissWindow

Button("open") {
  dismissWindow(id: "targetView")
  openWindow(id: "targetView")
}

Otherwise keeping track of the window lifecycle using the scene phase, combined with .onDisappear to track when a view is dismissed is more involved but also works. controlActiveState would be the preferred method, but that seems unavailable on visionOS (for now).

Upvotes: 1

Related Questions