Track NSWindow focus? (Swift, macOS) - swift

I'm trying to track a created windows focus so I can perform actions on those events.
I'm able to track a windows frame dimensions with the notification center using NSView.frameDidChangeNotification, but I cant find a correct way of using something like NSWindowDidResignKeyNotification or NSWindowDidResignMainNotification.
private func newWindow() {
let windowInfo = WindowInfo()
let contentView = ContentView()
.environmentObject(windowInfo)
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 350, height: 600),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered,
defer: false
)
window.isReleasedWhenClosed = false
window.contentView = NSHostingView(rootView: contentView)
window.contentView?.postsFrameChangedNotifications = true
window.makeKeyAndOrderFront(nil)
// vvv I'm trying to use a similar method of being notified when a created window loses/regains focus. vvv
// v v v
NotificationCenter.default.addObserver(forName: NSView.frameDidChangeNotification, object: nil, queue: nil) { (notification) in
windowInfo.frame = window.frame
}
}
Also, if theres some method for detecting window focus in SwiftUI, I could use that too. The notifications that actions are performed on are for my Views in SwiftUI

This was just a dumb oversight from me (it was pretty late at night), and I found the line I needed.
Adding NSWindow.didBecomeKeyNotification did the trick to properly catch the focus events.
Also changing object: nil to object: window made sure the focus events caught were only for the specific window, instead of all.

Related

Xcode SwiftUI window not shown after successful build

I get the following warning, on main.storyboard
Unsupported Configuartion:
Window Controller requires a content view controller
This is the custom class linked to the Window Controller, which is also the StoryBoard Entry Point,
import Cocoa
import SwiftUI
class FirstWindowController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
let contentView = ContentView(myListModel: MyListModel())
.environmentObject(UserData())
self.window?.contentView = NSHostingView(rootView: contentView)
}
}
This is inside the AppDelegate.swift which is annotated as #NSApplicationMain.
func applicationDidFinishLaunching(_ aNotification: Notification) {
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window”)
}
The main.storyboard, AppDelegate.swift, and FirstWindowController.swift are identical to those of a project which was launching and showing the application window with no problem. I just renamed it to something else, and removed the Core Data support from the code. But in this project, the window doesn’t show up after successful build.
I also have checked and compared all the configuration of the storyboard for both of these projects. They seem to be totally identical.
Any help for fixing this would be appreciated.
This might be due to a bug.
I removed all the other schemes but the one I was using in the project and the window is now loading.
Yet, the same warning is still there.

NSPanel not hiding when focus is lost

I am trying to create a window like Spotlight.
As in Spotlight it should hide when the background is clicked. I tried doing it unsuccessfully with NSWindow but I was lead to believe using NSPanel instead would solve the problem.
However, even when using NSPanel the window does not hide.
Here is the code I'm using.
let panel = NSPanel(contentRect: CGRect(x: 0, y: 0, width: 200, height: 200), styleMask: [.titled, .nonactivatingPanel], backing: .buffered, defer: true)
panel.level = .mainMenu
panel.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
panel.orderFrontRegardless()
It is due to used window level (.mainMenu which is above all windows), so you need to hide it explicitly via delegate methods
so assuming you create window/panel in your controller, make that controller a delegate of window
panel.delegate = self
and implement something like
extension ViewController { // << your controller class here
func windowDidResignKey(_ notification: Notification) {
if let panel = notification.object as? NSWindow {
panel.close()
}
}
}

MacOS SwiftUI, how to prevent window close action

So I have a swiftUI application, at some point I create a NSWindow and assign the contentView, like this:
// ////////////////////////////////////////////////////////
// Add token window
// ////////////////////////////////////////////////////////
let configurationView = ConfigurationView().environmentObject(store)
configurationWindow = NSWindow(
contentRect: NSRect(x:0, y: 0, width: 480, height: 500),
styleMask: [.titled, .closable, .miniaturizable, .fullSizeContentView],
backing: .buffered, defer: false
)
configurationWindow.center()
configurationWindow.setFrameAutosaveName("BSchauer")
let hostingController = NSHostingController(rootView: configurationView)
configurationWindow.contentViewController = hostingController
configurationWindow.makeKeyAndOrderFront(nil)
configurationWindow.setIsVisible(false)
...
// later on in the code
#objc func toggleConfigurationWindow() {
if self.configurationWindow.isVisible {
self.configurationWindow.setIsVisible(false)
if let button = self.statusBarItem.button {
self.popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
}
} else {
self.configurationWindow.setIsVisible(true)
self.configurationWindow.contentViewController?.view.window?.becomeKey()
}
}
You see that the way I interact with the window to present it to the user is via the visible flag, now the problem is when the window is shown and closed via the close button on the top bar, the window get somehow unmounted(?) and the next time the user tries to interact with the app and re-open the window I get a segmentation fault.
One of the things I tried was to instead of setting the visibility to false, just re-create the window again, but I still get the segmentation error.
All the previous answers I found are dealing with the old way of dealing with the NSViewController and overriding the windowShouldClose method, but I cannot seem to get that working.
TL:DR: When the user presses the red close button on the window, instead of the window getting destroyed I just want to set its visibility to false
I made it work, no need to set the contentViewController, you can use the standard contentView:
configurationWindow.contentView = NSHostingView(rootView: configurationView)
and to disable the window getting released when closed:
configurationWindow.isReleasedWhenClosed = false
I would still be interested in knowing when the window closed, to maybe perform an action afterwards, but this still solves my problem

CGDisplayCreateImage only returns wallpaper and task bar

I am trying to get the pixel colors from the screen for a specified portion of the display.
I am using CGDIsplayCreateImage(_:) to do so, but for some reason instead of creating a screenshot of the currently open windows it just gives an image of the wallpaper and the task bar.
In order to visualize the screenshot I am using a SwiftUI view. In my application I actually don't need the screenshot displayed.
struct ScreenshotView: View {
let cgImage: CGImage
var body: some View {
Image(decorative: cgImage, scale: 3)
}
}
And the applicationDidFinishLaunching(_:) methods looks like this.
func applicationDidFinishLaunching(_ aNotification: Notification) {
let contentRect = NSRect(x: 0, y: 0, width: 240, height: 240)
// 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: ScreenshotView(cgImage: CGDisplayCreateImage(CGMainDisplayID())!))
window.makeKeyAndOrderFront(nil)
}
Since XCode and some other apps are open it should give me a screenshot of the current screen with all open windwos. Instead I get a screenshot of the wallpaper and the taskbar showing my apps name. No apps are showing and the dock is also missing.
Am I doing something wrong or is this a bug?
In this stackoverflow answers suggest that I am using the function correctly.
Even if this might be obvious to some people I will answer my own question here using the help of Ken Thomases.
Below you can find the settings I set within my target. It now works after every rebuild.
Make sure to deactivate automatic signing and set the signing certificate to 'Sign to Run Local'.
Hope this helps someone at some point.

How do I get a titlebar to show programmatically in NSWindow?

Im trying to develop an OS X cocoa application programmatically, and I'm trying to display a window with a title bar which displays the usual traffic light (close, minimise, fullscreen) options at the top.
However, when the window displays on the screen, there is just an empty window.
Here is the code I am using:
class AppDelegate: NSObject, NSApplicationDelegate {
let window = NSWindow(contentRect: NSMakeRect(10, 10, 200, 200),
styleMask: NSWindowStyleMask.closable,
backing: NSBackingStoreType.buffered, defer: true)
func applicationDidFinishLaunching(_ aNotification: Notification) {
self.titleVisibility = .visible;
self.titlebarAppearsTransparent = false;
self.isMovableByWindowBackground = true;
let controller = NSWindowController(window: window)
controller.showWindow(self);
}
I have tried different NSWindowStyleMask when constructing the NSWindow but to no success.
This is what I see:
I am using Xcode 8.3 on 10.12
So you need 4 style masks.
NSWindowStyleMask.closable
NSWindowStyleMask.miniaturizable
NSWindowStyleMask.resizable
NSWindowStyleMask.titled
To put them all into one, you can use array
[NSWindowStyleMask.closable, NSWindowStyleMask.miniaturizable, NSWindowStyleMask.resizable, NSWindowStyleMask.titled]
But try to write in swift style
[.closable, .miniaturizable, .resizable, .titled]