问题
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 acancel:
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