NSWindowDelegate not getting resize notifications - swift

In a macOS Cocoa app, I am trying to get notifications when my window is "corner dragged" to a new size by setting an NSWindow delegate. I am successfully getting notified of resize events when the window is initially created, but not when the window is later dragged to a new size. I can't figure out why not.
Here is my code:
class MyWindowController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
window?.delegate = self
}
}
extension MyWindowController: NSWindowDelegate {
func windowDidResize(_ notification: Notification) {
print("windowDidResize")
}
func windowWillResize(_ sender: NSWindow, to frameSize: NSSize) -> NSSize {
print("windowWillResize")
return frameSize
}
}
When the window is first created, I see this output:
windowWillResize
windowDidResize
which proves the delegate methods are working. However, when I then grab a corner of the window and resize it, I do not see any additional print output.
I have reviewed a number of similar questions on SO about getting these notifications (like this and this), and it seems like I am doing everything right. And yet I don't get the notifications on window corner drag resize. Does anyone know why?

Based on the comment from Willeke, I created a strong reference to the NSWindowController subclass in my AppDelegate (where the window is created) and it fixed the problem.
For people finding this in the future, this is how I created in the strong reference (in AppDelegate):
// Need to maintain a strong reference while window is open
var myWindowController : MyWindowController?
[...]
func funcThatCreatesWindow() {
[...]
self.myWindowController = storyboard.instantiateController(withIdentifier: storyboardID) as? MyWindowController
if self.myWindowController != nil {
myWindowController!.showWindow(nil)
}
}

Related

macOS Swift QuickLook Warning: setDelegate and setDataSource called while the panel has no controller

Xcode 11, Swift 5.1
I'm getting a couple warnings every time I open a file and view it with QuickLook (QL). It seems to be working fine, but I'm wondering if I can get rid of the warnings.
The warnings say:
-[QLPreviewPanel setDelegate:] called while the panel has no controller - Fix this or this will raise soon.
-[QLPreviewPanel setDataSource:] called while the panel has no controller - Fix this or this will raise soon.
I set up and use QL on an NSTableCellView like this:
import Quartz
class AttachmentCell: NSTableCellView, QLPreviewPanelDataSource, QLPreviewPanelDelegate{
var quickLookItem:URL!
#IBAction func clickPreview(_ sender: Any) {
guard let panel = QLPreviewPanel.shared() else{ return }
panel.delegate = self
panel.dataSource = self
panel.makeKeyAndOrderFront(self)
}
func numberOfPreviewItems(in panel: QLPreviewPanel!) -> Int {
return 1
}
func previewPanel(_ panel: QLPreviewPanel!, previewItemAt index: Int) -> QLPreviewItem! {
quickLookItem = URL(fileURLWithPath: "...").appendingPathExtension(...)
return quickLookItem as QLPreviewItem
}
}
The clickPreview method is on an NSButton I have in my table cell. The closest thing I could find was this, but I don't see how the responder chain is involved: QLPreviewPanel in tableview with issue: "has no controller"
I also tried setting up my delegate methods on my NSViewController instead, but the same warning shows up.
Any ideas how to solve this? Or can I safely ignore it?

window.windowController is nil inside windowWillClose() but it isn't inside viewDidAppear()

I've tried, without success, respond to events such as windowWillClose() and windowShouldClose() inside NSWindowController (yes conforming to NSWindowDelegate).
Later, to my surprise, I was able to receive those events if I make my contentViewController (NSViewController) conform to NSWindowDelegate.
Unfortunately, later on, found out that view.window?.windowController is nil inside windowWillClose() or windowShouldClose(), code:
override func viewDidAppear() {
super.viewDidAppear()
self.view.window?.delegate = self
self.view.window?.windowController // not nil!
}
func windowWillClose(_ notification: Notification) {
self.view.window?.windowController // nil!!
}
func windowShouldClose(_ sender: NSWindow) -> Bool {
self.view.window?.windowController // nil!!
return true
}
After realizing that view.window?.windowController is not nil inside viewDidAppear() the next thing I thought was that Swift garbage collected the controller, so I changed viewDidAppear() in a way that creates another reference of windowController thus preventing garbage collection on said object, code:
var windowController: NSWindowController?
override func viewDidAppear() {
super.viewDidAppear()
self.view.window?.delegate = self
windowController = view.window?.windowController
}
func windowWillClose(_ notification: Notification) {
self.view.window?.windowController // NOT nil
}
func windowShouldClose(_ sender: NSWindow) -> Bool {
self.view.window?.windowController // NOT nil
return true
}
My hypothesis turned out to be correct (I think).
Is this the same issue that is preventing me from receiving those events inside NSWindowController?
Is there another way I can achieve the same thing without creating more object references?
In order to post code, I use the Answer option even though it is more of a comment.
I added in NSViewController:
override func viewDidAppear() {
super.viewDidAppear()
parentWindowController = self.view.window!.windowController
self.view.window!.delegate = self.view.window!.windowController as! S1W2WC. // The NSWC class, which conforms to NSWindowDelegate
print(#function, "windowController", self.view.window!, self.view.window!.windowController)
}
I get print log:
viewDidAppear() windowController Optional()
and notification is passed.
But if I change to
override func viewDidAppear() {
super.viewDidAppear()
// parentWindowController = self.view.window!.windowController
self.view.window!.delegate = self.view.window!.windowController as! S1W2WC
print(#function, "windowController", self.view.window!, self.view.window!.windowController)
}
by commenting out parentWindowController, notification don't go anymore to the WindowController…
Edited: I declared in ViewController:
var parentWindowController: NSWindowController? // Helps keep a reference to the controller
The proposed solutions are, in my opinion, hacks that can cause serious problems with memory management by creating circular references. You definitely can make instances of NSWindowController work as the window’s delegate. The proper way is to wire it up correctly in either code or in Interface Builder in Xcode. An example of how to do it properly is offered here.
If the delegate methods are not called is because the wiring up is not done correctly.
Another thing that must be done in Swift is when you add the name of the NSWindowController subclass in Interface Builder in Xcode is to check the checkbox of Inherits from Module. If you fail to do this, none of your subclass methods will be called.

windowWillClose and button action not called Swift

I'm designing a mac app with Xcode 10 (beta) and I got an issue with the Preference Window Controller
I have in my Main.storyboard a NSWindowController of custom class PreferenceWindowController with a toolbar. Here are its connections :
Here is the full class :
class PreferenceWindowController: NSWindowController, NSWindowDelegate {
#IBAction func didClickAuthor(_ sender: Any) {
print("author")
}
#IBAction func didClickTypo(_ sender: Any) {
print("typo")
}
override func windowDidLoad() {
super.windowDidLoad()
}
func windowWillClose(_ notification: Notification) {
print("willClose")
}
}
The window is initiated via the AppDelegate class with this code :
let storyboard = NSStoryboard(name: "Main",bundle: nil)
if let wc = storyboard.instantiateController(withIdentifier: "PreferenceWindowController") as? PreferenceWindowController
{
wc.showWindow(self)
}
The window opens as expected, with the toolbar clickable, but no functions from PreferenceWindowController are called at all, neither the closing of the window, nor the clicks on the toolbar.
I checked every connections, every class name, and I really don't know what's wrong...
SOLUTION
The solution is to store the PreferenceViewController class inside the AppDelegate class as a variable.
My solution :
var preferenceWindowController:PreferenceWindowController? = nil
#IBAction func clickPreferences(_ sender: Any) {
if let wc = storyboard.instantiateController(withIdentifier: "PreferencesWindowController") as? PreferenceWindowController {
let window = wc.window
preferenceWindowController = wc
wc.showWindow(self)
}
}
Thank you for helping !
The comment above seems like it could be on the right track. Based on the code context you've included in your question, it looks like the window controller you create will only have a lifetime for that function call.
Try making the window controller an instance variable. This is normally how I wire things up in an App delegate that creates window controllers. It's a simple pattern that works well.

ViewController + Storyboard setting up validation with controlTextDidChange

Trying to setup validation for a few text fields in a new (and very small) Swift Mac app. Following various other topics here on SO and a few other examples, I can still not get controlTextDidChange to propagate (to my ViewController).
E.g: How to live check a NSTextField - Swift OS X
I have read at least a dozen variations of basically that same concept. Since none of the accepted answers seem to work I am just getting more and more confused by something which is generally a fairly simple task on most platforms.
I have controlTextDidChange implemented to just call NSLog to let me know if I get anything.
AppDelegate should be part of the responder chain and should eventually handle controlTextDidChange but I see nothing there either.
Using the current Xcode I start a new project. Cocoa app, Swift, Storyboard and nothing else.
From what I can gather the below isolated example should work. In my actual app I have tried some ways of inserting the ViewController into the responder chain. Some answers I found suggested it was not always there. I also tried manually adding the ViewController as the delegate in code theTextField.delegate = self
Nothing I have done seems to get text changed to trigger any events.
Any ideas why I have so much trouble setting up this delegation?
My single textfield example app
Storyboard is about as simple as it gets:
AppDelegate
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, NSTextFieldDelegate, NSTextDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func controlTextDidChange(notification: NSNotification) {
let object = notification.object as! NSTextField
NSLog("AppDelegate::controlTextDidChange")
NSLog("field contains: \(object.stringValue)")
}
}
ViewController
import Cocoa
class ViewController: NSViewController, NSTextFieldDelegate, NSTextDelegate {
#IBOutlet var theTextField: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func controlTextDidChange(notification: NSNotification) {
let object = notification.object as! NSTextField
NSLog("ViewController::controlTextDidChange")
NSLog("field contains: \(object.stringValue)")
}
}
I think the samples you're following are a bit out-of-date.
Try...
override func controlTextDidChange(_ notification: Notification) {
...as the function definition for your method in your NSTextFieldDelegate.

What is the correct way to make a NSWindowController Singleton in Swift?

I have a sample project as:
https://github.com/ericgorr/nspanel_show.git
My project is a storyboard, document based application. I would like to use a custom segue to toggle the visible state of the inspector window. What I have should work, but I cannot quite determine how to make the inspector window a singleton.
I believe I should start with:
class InspectorWindowController: NSWindowController
{
static let sharedInstance = InspectorWindowController()
// override func init()
// {
//
// }
override func windowDidLoad()
{
super.windowDidLoad()
NSLog( ":::: %#", InspectorWindowController.sharedInstance );
}
}
But exactly what the initialization should look like in my situation is escaping me, especially since the window is inside of a storyboard.
You can select the window controller from the window controller scene and in the attributes inspector select Single from the pop up under Presentation. This will ensure the show segue only uses a single instance of the window controller. See this answer for more information.
Here's how I would modify your code:
In Main.storyboard give your InspectorWindowController an identifier, such as "Inspector Window Controller"
In InspectorWindowController, implement your singleton as follows:
static let shared: InspectorWindowController = {
let storyboard = NSStoryboard(name:"Main", bundle: nil)
let controller = storyboard.instantiateController(withIdentifier: "Inspector Window Controller")
return controller as! InspectorWindowController
}()
In Main.storyboard delete the segue from WindowController to InspectorWindowController
In WindowController replace the showMyPanel() and hideMyPanel() IBActions with:
#IBAction func toggleInspectorPanel( _ sender: AnyObject ) {
let inspectorWindow = InspectorWindowController.shared.window!
if inspectorWindow.isVisible {
inspectorWindow.orderOut(self)
} else {
inspectorWindow.makeKeyAndOrderFront(self)
}
}
Also in WindowController, remove the NSLog() call from windowDidLoad(). It causes a recursive call to the InspectorWindowController.shared initialization code.
In Main.storyboard link the Inspector toolbar button to toggleInspectorPanel()
The InspectorWindowController.shared singleton will be initialized, and the inspector panel loaded (but not shown), the first time it is referenced.