How to Open a Grid of Windows in visionOS

We can open windows placed relative to existing windows, creating custom grid formations.

Overview

Let’s build on the Synced Window Sets example. Imagine we want to create a grid of secondary windows around the main window.

Freeform mockup of the layout formation we want to create.

We can use defaultWindowPlacement to open four of these relative to the main window. We have an easy answer for top, bottom, leading, and trailing.

// Example of the Top Window
WindowGroup(id: "TopCenter") {
    Text("↖️")
        .font(.system(size: 128))
        .persistentSystemOverlays(.hidden)
}
.defaultSize(CGSize(width: 300, height: 300))
.defaultWindowPlacement { _, context in
    if let mainWindow = context.windows.first(where: { $0.id == "MainWindow" }) {
        return WindowPlacement(.above(mainWindow))
    }
    return WindowPlacement(.none)
}

So how about those two windows in the top corners? visionOS doesn’t have a .topLeading or.topTrailing placement so we need to get creative.

In this example, we’ll use the new top window to place the top leading and trailing windows.

// Top Leading (leading of TopCenter)
WindowGroup(id: "TopLeading") {
    Text("↖️")
        .font(.system(size: 128))
        .persistentSystemOverlays(.hidden)
}
.defaultSize(CGSize(width: 300, height: 300))
.defaultWindowPlacement { _, context in
    if let topWindow = context.windows.first(where: { $0.id == "TopCenter" }) {
        return WindowPlacement(.leading(topWindow))
    }
    return WindowPlacement(.none)
}

// Top Trailing (trailing of TopCenter)
WindowGroup(id: "TopTrailing") {
    Text("↗️")
        .font(.system(size: 128))
        .persistentSystemOverlays(.hidden)
}
.defaultSize(CGSize(width: 300, height: 300))
.defaultWindowPlacement { _, context in
    if let topWindow = context.windows.first(where: { $0.id == "TopCenter" }) {
        return WindowPlacement(.trailing(topWindow))
    }
    return WindowPlacement(.none)
}

We need to make sure the top window is open first, so we’ll loop over an array, opening these secondary windows from top to bottom, waiting a short time between openWindow calls.

This doesn’t guarantee the windows are open. In a production app, we may want to have each window report its open status before proceeding. We could to that by using Scene Phase to write to a central store that all windows can share.

let windowSequence: [[String]] = [
    ["BottomCenter"],
    ["MiddleLeading", "MiddleTrailing"],
    ["TopCenter"],
    ["TopLeading", "TopTrailing"],
]
Task {
    for group in windowSequence {
        for id in group {
            openWindow(id: id)
        }
        try? await Task.sleep(for: .milliseconds(150))
    }
}

Video Demo

See it in action. Whenever we open the secondary windows, we create this grid from top to bottom. The uses can move the main window at any time. Our window grid will close and reopen relative to the new position of the main window.

Video demo of a grid formation of windows placed around a main window

Hopefully visionOS 27 will bring better support for groups or sets of windows, but for now we can use techniques like this.

Sample code for this post is available in Garden035 in Step Into Examples on GitHub

Support our work so we can continue to bring you new examples and articles.

Questions or feedback?