NSWindow contentView not cover full window size - macOS & SwiftUI
Asked Answered
H

4

17

I'm starting a new macOS app with SwiftUI but I have a big problem. The app needs a full size contentView (underneath titleBar) but I can't accomplish. On a new project using Storyboards works fine, but with SwiftUI not.

My code: enter image description here

Result: enter image description here

And it should look like this: enter image description here

Any ideas? Thanks!

Halfpint answered 14/2, 2020 at 0:59 Comment(2)
And what's wrong with it? Do you want a window without titlebar?Mireielle
Yes, I need a window without titlebar but with close, minimize and resize buttons. Two views (red & black) must be extend under titlebar and buttonsHalfpint
M
22

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)
}
Mireielle answered 16/2, 2020 at 18:59 Comment(5)
It is interesting that specifying the edges outside of ContentView has a different effect. That is either a bug or SwiftUI has more composition problems than I thought. Time for more experiments :) Thanks for your answer!Depicture
For the record: If I have a NavigationView inside the ContentView I still need an edgesIgnoringSafeArea on the NavigationView as well.Depicture
Also I had add this window.isReleasedWhenClosed = false to prevent crashing while closing the window.Grievance
Unfortunately, this doesn't appear to work on macOS 12. I'm unable to make the sidebar assume the full window height.Drysalter
@Drysalter I just ran into issues with material / visual effect views not covering the whole window. I assume your sidebar is also a material / visual effect view? When debugging I noticed that using a Color it worked fine, but with a material / visual effect I had to use .frame(minWidth: ,minHeight:).Cancroid
D
5

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)
  }
}
Depicture answered 16/2, 2020 at 13:19 Comment(5)
The right way is to use NagivationView. In macOS you shouldn't use HStack for navigation. It works fine with your solution, but not with NavigationView. I leave this answer in standby pending other options. Thank you!Halfpint
You didn't say anything about navigation :) I have updated my answer.Depicture
How is it less accurate?Depicture
It's difficult to evaluate. Both answers are correct, but the other has less code, more general, applies to whole contentView, etc I've thought a lot about what answer accept as best. If I could, I'd give the points to both of you. I'm sorry... :(Halfpint
I can imagine, but I understand that you want to build a reputation.Halfpint
R
3

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())
Reginareginald answered 21/11, 2020 at 14:45 Comment(0)
S
1

Minimal solution in pure SwiftUI.

@main
struct X_App: App {

var body: some Scene {
    WindowGroup {
        ContentView()
       .edgesIgnoringSafeArea(.top) 
           
    }.windowStyle(.hiddenTitleBar)
 }}
Soak answered 24/8, 2021 at 18:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.