Debugging NSBeep Error Sound in NSResponder Method Calls
I utilize NSResponder
actions in TableFlip to move the selection around. Naturally, neither a standard NSTableView
nor a custom NSResponder
implement default behavior for (most of) the methods I need to support arrow key movement, tab movement, and the usual shortcuts, like Cmd-Left to jump to the first cell in the current row. Or Alt + arrow keys to insert a row or column next to the selection. So I implemented this in a custom NSResponder
subclass.
Curiously, the app beeped when I hit Alt-Up or Alt-Down, the actions to insert a row above or below the current selection. I implemented moveToBeginningOfParagraph(_:)
to consume these events but still the system “input error” sound was audible while the key combo itself performed the action just fine.
How do you find out the source of problems like this? Well, this is how I dug in.
My setup:
- The custom
NSTableView
subclass overrideskeyDown(_:)
with a call tonextResponder?.interpretKeyEvents(_:)
– but not delegating tosuper
, thus consuming the event, to disable standardNSTableView
shortcuts. - Note that
keyUp(_:)
would in general produce a similar effect but probably beep for every shortcut;keyDown(_:)
has a special meaning in all this, so make sure to override this method. - The next responder implements
moveToBeginningOfParagraph(_:)
. This method is called just fine thanks tointerpretKeyEvents(_:)
being called. This method maps common shortcuts toNSResponder
method calls.
If you have a custom NSView
subclass, you can call self.interpretKeyEvents(_:)
, too, instead of pushing it to the next responder; it’s just that I want to bypass the table view in this particular case. NSTableView.interpretKeyEvents
results in unwanted behavior.
This is where the beep happened even though the Alt-Up/Alt-Down NSResponder
methods were present. The input error sound usually plays when you don’t implement a shortcut anywhere in the responder chain.
According to lore and the docs, performKeyEquivalent(with:) -> Bool
should be useful: you return true
to indicate that the receiver took care of the event selector passed to it. In theory, this should prevent the beep sound even when you don’t implement the NSResponder
method – but that didn’t work at all for me.
These are the steps I found useful to hunt for the source:
- Add a symbolic breakpoint in Xcode for symbol “NSBeep” in module “AppKit”. When you hit enter or run the program for the first time, the breakpoint should obtain a child item; that indicates the symbol is recognized. Run the program, hit the key combo, and Xcode should stop execution. Look at the stack trace to see from where the key interpretation reaches nirvana. – I found out that shortcut execution indeed reaches
interpretKeyEvents(_:)
but seemingly bypasses the rest of the responder chain which should handle the expectedmoveToBeginningOfParagraph(_:)
call. (At least when it beeps. Since the action is performed by the program, I know the method is invoked.) - Add
doCommandBySelector(_:)
in your target responder. Insert a manual breakpoint. Look at the value of theselector
parameter for every call.
The first step revealed that some methods were never called when the beep happened. The second step told me which selector was actually invoked for a single hit of Alt-Up:
moveBackward(_:)
moveToBeginningOfParagraph(_:)
Alt-Down produces a similar combo:
moveForward(_:)
moveToEndOfParagraph(_:)
Aha!
Once I added an empty implementation of moveBackward(_:)
and moveForward(_:)
, the beeps went away.
NSResponder
debugging isn’t much fun most of the time because of all the implicit behavior. Then again, you get a lot of useful stuff for free. Sheesh. At least now I know that doCommandBySelector(_:)
is a great opportunity to see what’s done behind the scenes because it’s a bottleneck every call has to pass.