How to create a SwiftUI floating window in macOS 15
Sponsored
Develop with RocketSim, Ship with Helm.
Helm Pro yearly subscribers now get a 30% discount on RocketSim thanks to contingent pricing on the App Store.
Up until WWDC 24, SwiftUI had no built-in way of creating floating windows or, in other words, windows that stay on top of everything on the user’s screen. This was a limitation that I faced when building QReate’s latest feature and that I had to rely on AppKit to implement:
During the What’s new in SwiftUI session, Apple introduced a new set of APIs for window management that, among other things, allow you to set the floating level of a specific window in your app through the use of view modifiers:
App.swift
import SwiftUI
@main
struct QReateApp: App {
var body: some Scene {
// ...
WindowGroup(id: "floating-qr-code-window", for: UUID.self) { $qrCodeId in
if let qrCodeId = qrCodeId {
FloatingPanelQRCode(id: qrCodeId)
}
}
// macOS 15.0, iOS unavailable, tvOS unavailable, watchOS unavailable, visionOS unavailable
.windowManagerRole(.associated)
// iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, visionOS 2.0
.windowLevel(.floating)
.windowStyle(.plain)
.windowResizability(.contentSize)
}
}
There is also a brand new modifier that allows you to decide the default position of the window when it is created:
App.swift
import SwiftUI
@main
struct QReateApp: App {
var body: some Scene {
// ...
WindowGroup(id: "floating-qr-code-window", for: UUID.self) { $qrCodeId in
if let qrCodeId = qrCodeId {
FloatingPanelQRCode(id: qrCodeId)
}
}
// macOS 15.0, iOS unavailable, tvOS unavailable, watchOS unavailable, visionOS unavailable
.windowManagerRole(.associated)
// iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, visionOS 2.0
.windowLevel(.floating)
.windowStyle(.plain)
.windowResizability(.contentSize)
// macOS 15.0, visionOS 2.0, iOS unavailable, tvOS unavailable, watchOS unavailable
.defaultWindowPlacement { content, context in
let displayBounds = context.defaultDisplay.visibleRect
let size = content.sizeThatFits(.unspecified)
let position = CGPoint(
x: displayBounds.midX - (size.width / 2),
y: displayBounds.maxY - size.height - 20
)
return WindowPlacement(position, size: size)
}
}
}