When developing macOS applications with SwiftUI, you might need to create floating windows that stay on top of other windows. While modern macOS versions (15+) make this straightforward with the .windowLevel(.floating)
modifier, supporting older versions requires a different approach. In this post, I’ll show you how to create floating windows that work across different macOS versions.
The Modern Approach (macOS 15+)
For the latest macOS versions, setting a window level is as simple as adding a modifier:
.windowLevel(.floating)
However, this won’t work on older versions of macOS. Let’s look at a more compatible solution.
The Cross-Version Solution
To support older macOS versions, we need to access the underlying NSWindow
and set its level manually. Here’s how to do it:
First, create a bridge to access the NSWindow:
import AppKit
import SwiftUI
struct WindowAccessor: NSViewRepresentable {
var callback: (NSWindow?) -> Void
func makeNSView(context: Context) -> NSView {
let view = NSView()
DispatchQueue.main.async {
self.callback(view.window)
}
return view
}
func updateNSView(_ nsView: NSView, context: Context) {
// No update needed
}
}
Then, use this accessor in your SwiftUI view:
struct ContentView: View {
var body: some View {
VStack {
Text("This window will float!")
.padding()
}
.background(
WindowAccessor { window in
if let window = window {
window.level = NSWindow.Level.popUpMenu
}
}
.frame(width: 0, height: 0)
)
}
}
How It Works
- The
WindowAccessor
creates anNSView
that can access its parent window - We use
DispatchQueue.main.async
to ensure the window is available when we try to access it - The callback provides us with the
NSWindow
instance - We set the window’s level to
.popUpMenu
to make it float above regular windows - The zero-sized frame ensures our accessor view doesn’t affect the layout
Window Level Options
You can use different window levels depending on your needs:
// Common window levels
window.level = NSWindow.Level.normal // Regular window
window.level = NSWindow.Level.floating // Floating window
window.level = NSWindow.Level.popUpMenu // Menu level
window.level = NSWindow.Level.modalPanel // Modal dialog level
window.level = NSWindow.Level.screenSaver // Screen saver level
Complete Example
Here’s a complete example of a floating window with some content:
import SwiftUI
import AppKit
struct FloatingWindowView: View {
var body: some View {
VStack(spacing: 20) {
Text("Floating Window")
.font(.title)
Text("This window will stay on top of other windows")
.multilineTextAlignment(.center)
Button("Close") {
NSApplication.shared.keyWindow?.close()
}
}
.frame(width: 300, height: 200)
.padding()
.background(
WindowAccessor { window in
if let window = window {
window.level = NSWindow.Level.floating
window.title = "Floating Window"
}
}
.frame(width: 0, height: 0)
)
}
}
Best Practices
- Choose the appropriate window level for your use case
- Consider user experience - floating windows can be intrusive
- Provide a way to close or minimize the floating window
- Test on different macOS versions to ensure compatibility
This approach gives you a reliable way to create floating windows (windows that always stay on top of other windows) that work across different macOS versions while maintaining a clean SwiftUI-style implementation.
Remember that floating windows should be used judiciously - they can be disruptive to the user’s workflow if overused. Consider whether your use case really requires a window to float above others before implementing this feature.