Scene modifiers control a window's default size, how far the user can resize it, and where it first appears. These apply on macOS, iPadOS, and visionOS, the platforms with real windows.
Size and Resizability
defaultSize sets the initial window size. windowResizability bounds user resizing:
.contentSize clamps the window between the content's minimum and maximum sizes, while
.contentMinSize enforces only the lower bound.
WindowGroup(id: "search") {
SearchFeatureView()
}
.defaultSize(width: 500, height: 800)
.windowResizability(.contentSize)
Placement
A window opened with openWindow lands in front of the last window by default.
defaultWindowPlacement returns a WindowPlacement instead. On visionOS you get positional
factories such as .trailing(window) and .utilityPanel, which sits slightly below the
presenting window.
.defaultWindowPlacement { content, context in
#if os(visionOS)
if context.windows.last?.id == "search" {
return WindowPlacement(.trailing(context.windows.last!))
} else {
return WindowPlacement(.utilityPanel)
}
#endif
}
On macOS those factories do not exist. Measure the content with sizeThatFits and compute a
point from the display bounds in the context.
let size = content.sizeThatFits(.unspecified)
let positionX = context.defaultDisplay.bounds.midX - (size.width / 2)
let positionY = context.defaultDisplay.bounds.maxY - size.height
return WindowPlacement(CGPoint(x: positionX, y: positionY), size: size)
Reacting to Window Drags
WindowDragGesture fires while the user moves the window. Pair it with @GestureState to, for
example, redact content during the drag.
@GestureState private var isWindowDragging = false
Text("Some text here")
.redacted(reason: isWindowDragging ? .placeholder : [])
.gesture(
WindowDragGesture()
.updating($isWindowDragging) { _, state, _ in
state = true
}
)