How to create a SwiftUI floating window in macOS 15

Sponsored
RevenueCat logo
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)
        }
    }
}