NSWindow contentView not cover full window size - macOS & SwiftUI - macos

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:
Result:
And it should look like this:
Any ideas?
Thanks!

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)
}
}

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())

Minimal solution in pure SwiftUI.
#main
struct X_App: App {
var body: some Scene {
WindowGroup {
ContentView()
.edgesIgnoringSafeArea(.top)
}.windowStyle(.hiddenTitleBar)
}}

Related

Prevent transparent NSWindow with a ContentView using blendMode from flickering

The following creates a transparent NSWindow with a ContentView that uses blendMode to create a colour filter overlay effect so that everything behind the window appears blended (grey monochrome in this instance). It's working as expected except when the window is not active or being dragged in which case the ContentView flickers between normal (no blending) and blended; the ContentView is also showing dirty in some cases, i.e. when inactive ContentView is partially rendering and not fully updated.
Am I missing something in terms of ContentView life-cycle / refresh in relation to NSWindow events, is my NSWindow setup correct, or is this a potential bug? Essentially, the issue doesn't occur when blendMode isn't used, as testing with a transparent NSWindow and semi-opaque ContentView behaves normally.
I'm using Xcode 12.5.1 on Big Sur 11.6.2, and targeting 10.15
Code to reproduce using the AppKit App Delegate lifecycle template:
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
.edgesIgnoringSafeArea(.top)
.blendMode(BlendMode.color)
// Create the window and set the content view.
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.isReleasedWhenClosed = false
window.center()
window.setFrameAutosaveName("Main Window")
window.isOpaque = false
window.backgroundColor = .clear
window.level = .floating
window.isMovable = true
window.isMovableByWindowBackground = true
window.titlebarAppearsTransparent = true
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
struct ContentView: View {
var body: some View {
Rectangle()
.fill(Color.black)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
UPDATE 7th March
Issue persists using Xcode 13.2.1 on Monterey 12.2.1, and targeting 12.2
UPDATE 8th March
Adding a default NSVisualEffectView background view results in much greater stability in that the view no longer flickers between opaque and transparent when the window is active and being dragged.
The only issue remaining is when switching between apps and the focus is lost which sometimes causes the view to become opaque, although refocusing the window fixes the problem.
A workaround is to enable hidesOnDeactivate on NSWindow, combined with applicationShouldHandleReopen, so the window disappears when focus is lost and the issue isn't visible to the user, but ideally the window should remain visible at all times until closed.
struct VisualEffectView: NSViewRepresentable {
func makeNSView(context: Context) -> NSVisualEffectView {
let view = NSVisualEffectView()
return view
}
func updateNSView(_ nsView: NSVisualEffectView, context: Context) {
}
}
struct ContentView: View {
var body: some View {
Rectangle()
.fill(Color.black)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(VisualEffectView())
}
}
This feels a bit hackish and I'm sure someone out there with greater knowledge has a more elegant solution, but adding a Timer to the view to force a redraw solves the flickering problem completely, and would therefore appear to answer the question. Note: this method also dispenses with the need for a dummy NSVisualEffectView.
struct ContentView: View {
#State var currentDate = Date()
let timer = Timer.publish(every: 1.0 / 60.0, on: .main, in: .common).autoconnect()
var body: some View {
ZStack {
Rectangle()
.fill(.black)
.frame(maxWidth: .infinity, maxHeight: .infinity)
Text("\(currentDate)")
.foregroundColor(.clear)
.onReceive(timer) { input in
currentDate = input
}
}
}
}

How to set a max width on a subview of HSplitView

I'm having some trouble with HSplitView on macOS, and I'm not sure if it's a bug or something I'm doing wrong.
I'm trying to create an app with a [Sidebar | Main Content | Inspector] layout. This is similar to the SF Symbols app, where you can view more information about a selected icon.
The approach I've taken is to have the Main Content and Inspector views be in an HSplitView. Having three views in a NavigationView only seems to use the Mail pattern of [List | List | Detail], which is not the layout I'm looking for.
The problem I'm seeing is that when I set a maxWidth on the Inspector view and drag the divider to the left, the Main Content view resizes to be smaller than the available space. The parent HSplitView doesn't resize, so I'm not sure if I'm using the wrong modifiers or using them in the wrong places.
I've setup a basic test case on github. If y'all could offer and help or guidance, that would be amazing.
Observed behaviour
Expected behaviour
import SwiftUI
struct A: View {
var body: some View {
VStack {
Spacer()
HStack {
Spacer()
Text("Pane A")
Spacer()
}
Spacer()
}
.frame(
minWidth: 200,
maxWidth: .infinity,
maxHeight: .infinity,
alignment: .leading
)
.background(Color.red)
.foregroundColor(.black)
.layoutPriority(1)
}
}
struct B: View {
var body: some View {
VStack {
Spacer()
HStack {
Spacer()
Text("Pane B")
Spacer()
}
Spacer()
}
.frame(
minWidth: 200,
idealWidth: 250,
maxWidth: 300,
maxHeight: .infinity
)
.background(Color.purple)
.foregroundColor(.black)
}
}
struct ContentView: View {
var body: some View {
HSplitView {
A()
B()
}
.frame(
minWidth: 0,
maxWidth: .infinity,
maxHeight: .infinity
)
.background(Color.blue)
}
}e
Thanks!
I've had the same problem for several days, and I solved it in a flash.
Need to drag View
HSplitView {
View1()
View2()
}
Intuitively, we will set the maximum width of View1, but when SwiftUI drags, View1 exceeds the maximum width, it will display an inexplicable error
HSplitView {
View1()
.frame(maxWidth: 300)
View2()
}
Solutions
Set the minimum width of View2, and let View automatically fill all spaces. There will be no drag problem
HSplitView {
View1()
View2()
.frame(minWidth: 500)
}
Embedding one split view inside other may work.
Some problems arise if using GeometryReader.
HSplitView
{
View1()
HSplitView
{
View2()
View3()
}
}

Core Data template app generates errors on save

While evaluating Core Data for a macOS app, I get errors logged to the console when the document is saved:
[logging-persist] cannot open file at line 39930 of [02c344acea]
[logging-persist] os_unix.c:39930: (0) openDirectory(/Users/seanrich/Desktop) - Undefined error: 0
The data is saved properly and there's no other indication of an issue, but that error is concerning.
This sample project demonstrates the issue. It's minimally changed from a template project (selecting SwiftUI as the interface, Core Data checked). There's a single entity, Item, with a single attribute, name. I added a minimal interface to add items into the NSManagedObjectContext:
struct ContentView: View {
#State private var name = ""
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: Item.entity(), sortDescriptors: [.byName]) var items: FetchedResults<Item>
var body: some View {
VStack {
HStack {
TextField("Name", text: $name)
Button("New") {
let item = Item(context: moc)
item.name = name
}
}
.padding()
VStack {
ForEach(items) { item in
Text(item.name ?? "No name")
}
}
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
The NSPersistentDocument subclass is unchanged from the template:
EDIT: I added a line to explicitly load the managedObjectModel or the SwiftUI view tries to fetch an entity that isn't there yet:
class Document: NSPersistentDocument {
override class var autosavesInPlace: Bool { true }
override func makeWindowControllers() {
_ = managedObjectModel
let contentView = ContentView().environment(\.managedObjectContext, self.managedObjectContext!)
// Create the window and set the content view.
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.isReleasedWhenClosed = false
window.center()
window.contentView = NSHostingView(rootView: contentView)
let windowController = NSWindowController(window: window)
self.addWindowController(windowController)
}
}
I thought it might be a permissions issue, but the sandbox is set to give read/write access to user-selected files. Couldn't find any mention of this issue anywhere which is surprising since it seems to be default behavior. Makes me think I'm missing something obvious.

How can I hide statusBar of a View/Window in macOS in SwiftUI?

I was trying to hide statusBar of a view but I noticed it does not work in macOS, also Blur did not worked also opacity did not work! it look like I am trying programming new language, apple said code in place and apply every where! why I can not done those simple task in macOS?
struct ContentView: View {
var body: some View {
HStack {
Button { print("OK was clicked!") } label: { Text("OK").frame(width: 100) }
Button { print("Close was clicked!") } label: { Text("Close").frame(width: 100) }
}
.frame(width: 400, height: 200)
.background(Color.white.opacity(0.4).blur(radius: 0.5))
//.statusBar(hidden: true)
}
}
import SwiftUI
#main
struct macOSApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Hope it helps you.
Thank you.
class AppDelegate: NSObject, UIApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the content view, to display the contents
let contentView = ContentView()
//to display content view beyond status bar.
.edgesIgnoringSafeArea(.top)
// Now, just 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")
//So, this does the maigc
//Hides the titlebar.
window.titlebarAppearsTransparent = true
window.titleVisibility = .hidden
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
}
}

SwiftUI scroll view partially displays last item in Mac app

How do I properly setup a ScrollView with SwiftUI in a Mac app? The example below clips the last item.
import SwiftUI
struct NameRow: View {
var name: String
var body: some View {
VStack {
Spacer()
Text("\(name)")
Spacer()
Divider()
}.frame(width: 100, height: 40)
}
}
struct ContentView: View {
let names = ["Homer", "Marge", "Lisa", "Bart", "Maggie", "Krusty", "Burns", "Nelson", "Otto"]
var body: some View {
VStack {
ScrollView {
ForEach(names, id: \.self) { name in
NameRow(name: name)
}
}
}.frame(width: 100, height: 160)
}
}
As shown in the image, the last item is clipped by the window. This view is scrolled to the bottom but the "Otto" item does not fully display.
If you remove the .fullSizeContentView from the window initialization in AppDelegate.swift so it looks like the following, then it will fully display the list.
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable],
backing: .buffered, defer: false)
I don't know if this is a bug or by design.

Resources