How to close window (NSWindowController) by hitting the ESC key?

拟墨画扇 提交于 2020-02-23 08:01:39

问题


Issue

I would like the user being able to close a window by hitting the ESC key but I can't get it to work in this specific case, hitting ESC triggers an error sound (the "no you can't do that" macOS bloop) and nothing happens.

Context

I'm making a subclass of NSWindowController which itself creates an instance of a subclass of NSViewController and sets it in a view. Both controllers have their own xib file.

NSWindowController:

final class MyWindowController: NSWindowController, NSWindowDelegate {

    @IBOutlet weak var targetView: MainView!

    let myVC: MyViewController!
    var params: SomeParams!

    override func windowDidLoad() {
        super.windowDidLoad()
        myVC = MyViewController(someParams: params)
        myVC.view.setFrameSize(targetView.frame.size)
        myVC.view.setBoundsSize(targetView.bounds.size)
        targetView.addSubview(myVC.view)
    }

    override var windowNibName: String! {
        return "MyWindowController"
    }

    convenience init(someParams params: SomeType) {
        self.init(window: nil)
        self.params = params
    }

}

NSViewController:

final class MyViewController: NSViewController {

    convenience init(someParams params: SomeType) {
        // do stuff with the params
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // configure stuff for the window
    }

}

What I've tried

I suppose that my issue is that the MyWindowController NSWindow is the .initialFirstResponder when I would want the content of the targetView (an NSTableView) to be the first responder - this way I could use keyDown, I guess, and send the close command to the window from there. This doesn't seem optimal, though.

I've tried forcing the view controller views into being the first responder by using window?.makeFirstResponder(theView) in the windowDidLoad of MyWindowController but nothing ever changes.

I've also tried adding this to MyWindowController:

override func cancelOperation(_ sender: Any?) {
    print("yeah, let's close!")
}

But this only works if the user clicks first on the background of the window then hits ESC, and it still emits the error sound anyway. Which is actually what made me think that the issue was about the first responder being on the window.

Question

How would you achieve that? Of course, I know that the user can already close the window with CMD+W, but I'd really like to sort out this issue nonetheless.

Note that the code example is in Swift but I can also accept explanations using Objective-C.


回答1:


The documentation of cancelOperation explains how cancelOperation should work:

This method is bound to the Escape and Command-. (period) keys. The key window first searches the view hierarchy for a view whose key equivalent is Escape or Command-., whichever was entered. If none of these views handles the key equivalent, the window sends a default action message of cancelOperation: to the first responder and from there the message travels up the responder chain.

If no responder in the responder chain implements cancelOperation:, the key window searches the view hierarchy for a view whose key equivalent is Escape (note that this may be redundant if the original key equivalent was Escape). If no such responder is found, then a cancel: action message is sent to the first responder in the responder chain that implements it.

NSResponder declares but does not implement this method.

NSWindow implements cancelOperation: and the next responder, the window controller, isn't checked for an implementation of cancelOperation:. The cancel: message does arrive at the window controller. Implementing

- (void)cancel:(id)sender
{
    NSLog(@"cancel");
}

will work. The cancel: message isn't inherited from a superclass so autocompletion doesn't suggest it.




回答2:


This worked for me in Xcode 10 and Swift 4.2:

@objc func cancel(_ sender: Any?) {
    close()
}

I tried it before but without the @objc part and it didn't work. So don't omit it.




回答3:


When I needed such behavior I implemented it by overriding keyDown: of the NSWindow object.

I.e. something like the following:

- (void)keyDown:(NSEvent *)theEvent
{
    int k = [theEvent keyCode];
    if (k == kVK_Escape)
    {
        [self close];
        return;
    }

    [super keyDown:theEvent];
}


来源:https://stackoverflow.com/questions/42393336/how-to-close-window-nswindowcontroller-by-hitting-the-esc-key

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!