Make NSWindow remain "float" when space changes in swift - swift

I currently have:
let window = NSWindow(contentRect: NSRect(x: 300, y: 300, width: 200, height: 200), styleMask: [.borderless], backing: .buffered, defer: true)
window.backgroundColor = NSColor.green
window.level = .floating
window.collectionBehavior = [.stationary, .canJoinAllSpaces, .fullScreenAuxiliary]
window.makeKeyAndOrderFront(nil)
Current behaviour:
When space changes (moving between full screen windows) the window moves relative to the desktop/space .
Expected behaviour:
I want the window to behave much like the notifications window in macOS, where the window "floats" above the screen even when active space changes.

Related

MacOS App NSWindow over whole macOS UI including cursor

I want my NSWindow over the macOS cursor. Is this possible? I managed to get the window above most of the UI, including Mission Control and the Menubar, but the cursor is still above the window. My code:
window = NSWindow(
contentRect: NSScreen.main!.frame,
styleMask: [.borderless],
backing: .buffered, defer: true)
window.backgroundColor = NSColor.clear
window.isOpaque = false
window.isReleasedWhenClosed = false
window.level = NSWindow.Level.mainMenu + 1
window.collectionBehavior = [NSWindow.CollectionBehavior.canJoinAllSpaces, .stationary, .ignoresCycle]
window.minSize = NSSize(width: NSScreen.main!.frame.width, height: NSScreen.main!.frame.height)
window.orderFront(nil)
window.ignoresMouseEvents = true

SwiftUI multiple windows closing will cause crash

I am creating a second window in my SwiftUI app for Mac.
This is how I call my second window.
let window = NSWindow(contentRect: NSRect(x: 20, y: 20, width: 480, height: 300), styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Add Person")
window.contentView = NSHostingView(rootView: AddPerson())
window.makeKeyAndOrderFront(nil)
This works fine. However, when I try to close that window, the app is crashing.
I am calling this, to close the window.
NSApplication.shared.keyWindow?.close()
I think there is a problem with opening two windows. Is my opening call correct?
Edit: I need to set the window.number when I am creating that window. How can I set that? I haven't found anything.
Well, I don't think it is because of window itself (I tested your case on Xcode 11.3 with just simple text & button content and it works), but due to some content and/or active objects (managers, etc.)
Anyway, you can try instead of force close (as in provided snapshot) to close via action
NSApp.keyWindow?.performClose(nil)
it does the same as clicking close button on window's titlebar.
Update: store below window as member, not a local variable
let window = NSWindow(contentRect: NSRect(x: 20, y: 20, width: 480, height: 300), styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], backing: .buffered, defer: false)
like main window
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow! // << default, main window
var window2: NSWindow! // << other window (as example)
... // somewhere below
window2 = NSWindow(contentRect: NSRect(x: 20, y: 20, width: 480, height: 300), styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], backing: .buffered, defer: false)

How to convert a Swift UI View to a NSImage

I am trying to use a SwiftUI View as a NSCursor on MacOS.
Using SwiftUI I am constructing a view that I then convert to an NSView using NSHostingView. Now I am trying to convert that to a NSImage via a NSBitmapImageRep. For some reason I always get an empty png when I inspect the variables while setting breakpoints.
(For now the setup of the cursor is done in the AppDelegate because I am currently just trying to get this to work.)
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
let contentRect = NSRect(x: 0, y: 0, width: 480, height: 480)
// Create the window and set the content view.
window = NSWindow(
contentRect: contentRect,
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
let myNSView = NSHostingView(rootView: ContentView()).bitmapImageRepForCachingDisplay(in: contentRect)!
NSHostingView(rootView: ContentView()).cacheDisplay(in: contentRect, to: myNSView)
let myNSImage = NSImage(size: myNSView.size)
myNSImage.addRepresentation(myNSView)
self.window.disableCursorRects()
// var myName = NSImage.Name("ColorRing")
// var myCursorImg = NSImage(named: myName)!
//
// var myCursor = NSCursor(image: myCursorImg, hotSpot: NSPoint(x: 10, y: 10))
var myCursor = NSCursor(image: myNSImage, hotSpot: NSPoint(x: 0, y: 0))
myCursor.set()
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
When executing the part that is commented out, I get the mouse cursor from an external png file.
When executing the code shown above I always get an empty mouse cursor with the size 480x480.
In debug mode I can see, that myNSView is an empty png.
I am pretty sure I misunderstand the documentation and therefore misuse cacheDisplay(in:to:) and bitmapImageRepForCachingDisplay(in:).
The documentation tells me to use the NSBitmapImageRep from bitmapImageRepForCachingDisplay(in:) in cacheDisplay(in:to:). But for some reason I simply can not get this to work.
Does anyone have an idea what I am doing wrong?
To answer my own question:
It seems like the view that one wants to convert to an NSImage needs to be layed out inside of a window. Otherwise it probably does not know how large the view needs to be inside the rect.
The following code works for me:
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
let contentRect = NSRect(x: 0, y: 0, width: 480, height: 480)
// Create the window and set the content view.
window = NSWindow(
contentRect: contentRect,
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
// CHANGED ----------
let newWindow = NSWindow(
contentRect: contentRect,
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
newWindow.contentView = NSHostingView(rootView: contentView)
let myNSBitMapRep = newWindow.contentView!.bitmapImageRepForCachingDisplay(in: contentRect)!
newWindow.contentView!.cacheDisplay(in: contentRect, to: myNSBitMapRep)
// CHANGED ----------
let myNSImage = NSImage(size: myNSBitMapRep.size)
myNSImage.addRepresentation(myNSBitMapRep)
self.window.disableCursorRects()
var myCursor = NSCursor(image: myNSImage, hotSpot: NSPoint(x: 0, y: 0))
myCursor.set()
}
Only lines between the two //CHANGED ----- comments have been changed for a working solution. I am simply embedding the view inside a window before applying the same methods as before.
Hope this helps some people in the future.

In OS X, how to get the window frame size correctly?

I try to create an OS X window programmatically as below:
print("creating window frame=\(winFrame)")
let window = NSWindow(contentRect: winFrame, styleMask: [NSWindowStyleMask.resizable, NSWindowStyleMask.closable, NSWindowStyleMask.miniaturizable, NSWindowStyleMask.titled], backing: NSBackingStoreType.buffered, defer: false)
print("after window frame=\(window.frame)")
I was expecting these two output should be the same, however here is the output
creating window frame=(1556.0, 193.0, 421.0, 646.0)
after window frame=(1556.0, 193.0, 421.0, 668.0)
The difference is that the height is increased from 646 to 668.
Why is there such a difference and how to get the right number?
The window's contentRect is the rect, in global screen coordinates, to be occupied by the contentView of the window. This rect does not include the window's title bar. The window's frame does include the title bar.
If you have a desired window frame, you can compute the corresponding content rect, then use the content rect to create the window:
let winFrame = ...
let styleMask: NSWindowStyleMask = [.resizable, .closable, .miniaturizable, .titled]
let contentRect = NSWindow.contentRect(forFrameRect: winFrame, styleMask: styleMask)
let window = NSWindow(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false)
Or you can just set the window's frame after you create it, like this:
let winFrame = ...
let styleMask: NSWindowStyleMask = [.resizable, .closable, .miniaturizable, .titled]
let window = NSWindow(contentRect: .zero, styleMask: styleMask, backing: .buffered, defer: false)
window.setFrame(winFrame, display: true)
While creating the NSWindow you set the contentRect which is not the frame.
An NSWindow object is defined by a frame rectangle that encloses the entire window, including its title bar, border, and other peripheral elements (such as the resize control), and by a content rectangle that encloses just its content area.
-- From the How Windows Work page out of the Window Programming Guide
So you are getting the correct frame size of the window. Either you want to create a smaller one by passing in a smaller contentRect or you want to access the contentView.frame property.

Create a new window with NSWindow

I'd like to create a new window programmatically. I have the following code, and it builds, but my window doesn't show up. The only way I can make it visible is by adding it as a child window to the default 'window'. How can I make 'win' be an independent window?
#IBOutlet var window: NSWindow
func applicationDidFinishLaunching(aNotification: NSNotification?) {
var win = NSWindow(contentRect: NSMakeRect(100, 100, 600, 200),
styleMask: NSResizableWindowMask,
backing: NSBackingStoreType.Buffered, defer: true)
window.addChildWindow(win, ordered:NSWindowOrderingMode.Above)
}
What about adding:
win.makeKeyAndOrderFront(win)
For me on OSX (not iOS) using Swift and writing in vim
let win = NSWindow(contentRect: NSMakeRect(100, 100, 600, 200),
styleMask: NSResizableWindowMask,
backing: NSBackingStoreType.buffered, defer: true)
win.makeKeyAndOrderFront(win)
pops up a window
You also need a NSWindowController to display the window:
let window = NSWindow(...)
let controller = NSWindowController(window: window)
controller.showWindow(self)