NSWindow contentView not cover full window size - macOS & SwiftUI
Just for more information in the case of SwiftUI App life cycle.
You need to set the window style to HiddenTitleBarWindowStyle :
WindowGroup {
ContentView()
}.windowStyle(HiddenTitleBarWindowStyle())
I just used the following variant in AppDelegate
, the content of ContentView
and others can be any
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
.edgesIgnoringSafeArea(.top) // to extend entire content under titlebar
// Create the window and set the content view.
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .texturedBackground, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
window.titlebarAppearsTransparent = true // as stated
window.titleVisibility = .hidden // no title - all in content
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
}
The safe area does not extend underneath a transparent title bar. You can use edgesIgnoringSafeArea
to tell your content view edges to ignore the safe area. Something that resembles your example:
struct ContentView: View {
var body: some View {
HStack(spacing: 0) {
Text("Hello, World!")
.frame(maxWidth: 200, maxHeight: .infinity)
.background(Color.red)
Text("Hello, World!")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.black)
}.edgesIgnoringSafeArea(.all)
}
}
Update:
If you want to use a NavigationView, you have to add edgesIgnoringSafeArea
to its contents as well:
struct ContentView: View {
var body: some View {
NavigationView {
Text("Hello, World!")
.frame(maxWidth: 200, maxHeight: .infinity)
.background(Color.red)
.edgesIgnoringSafeArea(.all)
Text("Hello, World!")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.black)
.edgesIgnoringSafeArea(.all)
}.edgesIgnoringSafeArea(.all)
}
}
Unfortunately, this will initially show a title bar at the moment, apparently until you force a full window redraw. Once you move the window to a different display or hide and show it again, the title bar disappears. So, my guess is that this will be fixed at some point.
Right now, you can programmatically force a hide & show by adding
DispatchQueue.main.async {
self.window.orderOut(nil)
self.window.makeKeyAndOrderFront(nil)
}
after window.makeKeyAndOrderFront(nil)
in applicationDidFinishLaunching
. It will add a very short animation however. If it bothers you, you may be able to turn that off with NSWindow.animationBehavior
or something like that.
Update 2: Apparently, if you remove the initial window.makeKeyAndOrderFront(nil)
and replace it with the dispatch queue logic above it won't animate. So in the end you'll have
func applicationDidFinishLaunching(_ aNotification: Notification) {
let contentView = ContentView()
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView, .texturedBackground],
backing: .buffered, defer: false)
window.titlebarAppearsTransparent = true
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: contentView)
// window.makeKeyAndOrderFront(self) <- don't call it here
DispatchQueue.main.async {
self.window.orderOut(nil)
self.window.makeKeyAndOrderFront(nil)
}
}