Master Window Sizing in visionOS: The Ultimate SwiftUI Guide for the iOS Developer
If you are an experienced iOS developer, you have spent years programming with one immovable constant: screen size is fixed. Whether it’s an iPhone SE or a 12.9-inch iPad Pro, the hardware dictates the limits. Your job in Xcode was to adapt your content to those predefined rectangles.
Welcome to spatial computing. In visionOS, that certainty disappears. The canvas is infinite, and windows can be any size, placed anywhere, and most importantly, the user can resize them at will.
In this tutorial, we will break down everything you need to know about window sizing in visionOS and SwiftUI. You will learn how to configure initial sizes, establish constraints, handle 3D volumes, and adapt your Swift programming to create fluid spatial experiences.
The Paradigm Shift: From UIWindow to Spatial WindowGroup
In classic iOS development with UIKit or SwiftUI, the Window was usually synonymous with the device screen. In visionOS, an application can have multiple windows open simultaneously, and each is an independent floating entity.
To start, we must understand how SwiftUI defines a window in this new operating system. It all begins in your App’s main file, inside the WindowGroup.
import SwiftUI
@main
struct VisionOSApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
// This is where the sizing magic happens
.defaultSize(width: 600, height: 400)
}
}
Unlike iOS, where the system launches the app in full screen, in visionOS you suggest the initial size. But this is just the tip of the iceberg.
Controlling Initial Size: .defaultSize
The .defaultSize modifier is your first tool in Xcode to suggest how your application should be presented. It is crucial to understand that this is a suggestion. The system will try to respect it, but factors such as user position or the presence of other apps might influence it.
There are several ways to define this size in your Swift programming:
1. Fixed Size in Points
The most common way for flat windows (“slate” style). Remember that in visionOS, points translate to physical meters depending on distance, but for UI design, you still think in logical points.
WindowGroup {
ContentView()
}
.defaultSize(width: 800, height: 600)
2. Size for Volumes (3D)
If you are developing an application that uses a volume (a bounded 3D box), you need to define three dimensions: width, height, and depth.
WindowGroup(id: "SolarSystem") {
SolarSystemView()
}
.windowStyle(.volumetric)
.defaultSize(width: 1.0, height: 1.0, depth: 1.0, in: .meters)
Note for the iOS developer: Here we introduce explicit physical units (.meters). This is vital for objects that must have a realistic scale in the user’s world.
Resizability Constraints: .windowResizability
One of the biggest challenges in window sizing in visionOS and SwiftUI is handling what the user can do. By default, windows in visionOS have a “resize handle” in the bottom corner that allows the user to stretch the window freely.
What happens if your design breaks if the window is too narrow? Or if it looks ridiculous if it’s too wide? Enter .windowResizability.
Locking Size to Content
If you want your window to fit exactly to the size of your SwiftUI view and not allow the user to change it, use .contentSize.
WindowGroup {
VStack {
Text("This window cannot be stretched")
Image("Logo")
}
.frame(width: 400, height: 300) // The view size rules
}
.windowResizability(.contentSize)
This is useful for dialogs, custom alerts, or small utility tools.
Minimum and Maximum Limits
Often, you will want to allow some flexibility, but within logical limits. You can use the frame(minWidth:maxWidth:) modifier inside your view, and tell the window to respect those limits.
struct ResizableContentView: View {
var body: some View {
NavigationSplitView {
List(1...10, id: \.self) { item in
Text("Item \(item)")
}
} detail: {
Text("Detail")
}
// We define the logical limits of our UI
.frame(minWidth: 600, maxWidth: 1200, minHeight: 400, maxHeight: 800)
}
}
// In your App.swift
WindowGroup {
ResizableContentView()
}
.windowResizability(.contentSize)
By combining .windowResizability(.contentSize) with frame constraints on the root view, visionOS understands that the resize handle should stop when the user reaches those minimum or maximum sizes.
Adapting Layout with GeometryReader
As a SwiftUI expert, you already know GeometryReader. In visionOS, it is more important than ever. Since the user can resize the window in real-time, your design must be responsive.
A common technique in Swift programming for visionOS is to switch the layout from HStack to VStack depending on the available width, similar to how you would on iPad with Slide Over.
struct ResponsiveView: View {
var body: some View {
GeometryReader { proxy in
if proxy.size.width > 600 {
HStack {
SideBar()
MainContent()
}
} else {
VStack {
TopBar()
MainContent()
}
}
}
}
}
This ensures that your application feels native and well-constructed, regardless of how the user decides to configure their workspace.
Depth and Scale: The Z-Axis
This is where the iOS developer usually stumbles. Windows in visionOS have depth, even flat ones. When you use .glassBackgroundEffect(), the system adds thickness and shadows.
If you are working with Volumetric Windows, size is literal. A 1-meter cube is huge in a small room. You must use the Xcode preview tool to understand human scale.
Resizing Volumes
For volumes, resizability works differently. Sometimes you want the content to scale (get visually bigger) and other times you want the container to get bigger to show more content.
WindowGroup {
My3DModelView()
}
.windowStyle(.volumetric)
.defaultSize(width: 0.5, height: 0.5, depth: 0.5, in: .meters)
.windowResizability(.contentSize) // Prevents the user from distorting the 3D model
Advanced Programming: Dynamic Resizing
What happens if you need to change the window size programmatically after it has opened? For example, the user taps a “View Details” button and the window needs to expand.
Until the initial versions of visionOS, you couldn’t force an arbitrary resize on an existing window as easily as `frame.size = newSize` in UIKit. Apple’s philosophy is user agency.
However, you can influence the size through SwiftUI state if you have configured .windowResizability(.contentSize).
struct DynamicWindowView: View {
@State private var isExpanded = false
var body: some View {
VStack {
Toggle("Expand window", isOn: $isExpanded)
if isExpanded {
Text("Additional content...")
.frame(height: 200)
}
}
.frame(width: 400, height: isExpanded ? 600 : 300) // The frame changes dynamically
.animation(.spring(), value: isExpanded)
}
}
If the window has the resizability property tied to content, changing the root view’s frame via a SwiftUI animation causes the physical visionOS window to animate smoothly to accommodate the new size. Pure SwiftUI magic!
Best Practices (Human Interface Guidelines)
To optimize your app and make it stand out on the App Store, follow these golden rules:
- Don’t use full screen: Do not try to fill the user’s field of view. Windows should be comfortable to view without moving the neck.
- Respect margins: The “Glass” material needs room to breathe. Use
.padding()generously. If your content touches the edges, the depth effect is lost and resizing looks strange. - Ergonomic sizes: A default width of 1280pt is usually a good maximum for readable content. Any wider and the user will have to turn their head to read from side to side.
- Consistency: If the user resizes your window, visionOS will remember that size the next time they open the app. Do not fight this by forcing a size at startup every time.
Troubleshooting in Xcode for Developers
When working with window sizing in visionOS and SwiftUI, you will encounter these common errors:
- Content gets clipped: You are likely using a fixed
.frameinside a resizable window. Switch tominWidthandmaxWidth. - The window does not resize with content: Make sure you have applied the
.windowResizability(.contentSize)modifier to theWindowGroupin yourApp.swiftfile. Without this, the window stays at its current size even if the internal content changes. - Misaligned Ornaments: If you change the window size drastically, check your Ornaments. Sometimes they need custom anchors so they don’t overlap with new content.
Conclusion
Mastering window sizing in visionOS and SwiftUI is the skill that will differentiate an average iOS developer from a spatial computing pioneer. The key is to stop thinking in static pixels and start thinking in fluid constraints and adaptable content.
The combination of WindowGroup, defaultSize, and windowResizability gives you total control over how your application feels in physical space.
If you have any questions about this article, please contact me and I will be happy to help you 🙂. You can contact me on my X profile or on my Instagram profile.