NSTableView text edition issue with OK button

Context


Mac OS X 10.8 and later


I have a view-based NSTableView with default NSTableCellView (i.e. a NSTextField) which is inside a dialog sheet. There are also a Cancel and a OK button with their default shortcuts set (respectively Escape and Return).


Problem


When the edition of a cell of the tableview is ended via the Return key, the sheet gets also closed. I don't want this.


Regression


This kind of problem does not happen with a cell-based NSTableView.


Investigation


When looking at the backtrace, the sheet gets closed because the OK button action is invoked via its shortcuts. What happens is that the edited NSTextField calls [self.window performKeyEquivalent:] when its textDidEndEditing: method is invoked with NSTextMovement == @(

NSReturnTextMovement)


I had a look at the reconstructed code of [NSTextField textDidEndEditing:] (via Hopper) and while I can find the [self.window performKeyEquivalent:]; I haven't been able to figure out whether there is a way to prevent this from happening.


- Playing with the didEndEditing and beginEditing NSTextField delegate methods/notifications looks like a deadend.

- Overiding performKeyEquivalent: for NSWindow subclasses is not recommended and is not producing any expected behaviors.


Question


Is there a way to get back the appropriate behavior we had with cell-based NSTableView (the edition of the text field ends and that's all) and disable the incorrect behavior of view-based NSTableView?


I'm probably missing something obvious.

Accepted Reply

I've found a solution that works at least on OS X 10.10. I haven't yet checked on other OS versions.


It requires to subclass NSTableCellView and make the subclass to "support" the NSTextFieldDelegate protocol.


Then you set the delegate of text field of the table cell view to be the table cell view.


Then in the NSTableCellView subclass, you add this:


- (BOOL)control:(NSControl *)inControl textView:(NSTextView *)inTextView doCommandBySelector:(SEL)inSelector
{
     if (inSelector==@selector(insertNewline:))
     {
          [inControl.window makeFirstResponder:self];

          return YES;
     }

     return NO;
}

Replies

Cannot you block the message to go up to the next responder ?


You should have a look here :

h ttps://developer.apple.com/library/content/documentation/Cocoa/Conceptual/EventOverview/EventHandlingBasics/EventHandlingBasics.html

Here's what the documentation for "textDidEndEditing:" says (developer.apple.com/documentation/appkit/nstextfield/1399420-textdidendediting):


"If the user ended editing by pressing Return, this method tries to send the receiver’s action to its target; if unsuccessful, it sends performKeyEquivalent(with:) to its NSView (for example, to handle the default button on a panel); if that also fails, the receiver simply selects its text."


That suggests you have to connect the text field to a target/action, in order for the text field to "consume" the Return. Set the text field's target to "First Responder", with a different action method from the one on the OK button. Then implement the action method in some NSResponder object that's actually in the responder chain at the time Return is pressed:


— One easy way is to put it in your NSWindowController subclass.


— It would be easier to put in a NSViewController subclass, but view controllers aren't in the responder chain prior to 10.10, so this doesn't work for you.


— You can subclass NSTableCellView.

"That suggests you have to connect the text field to a target/action, in order for the text field to "consume" the Return. Set the text field's target to "First Responder", with a different action method from the one on the OK button. Then implement the action method in some NSResponder object that's actually in the responder chain at the time Return is pressed:"


- I have alreay an action and target set for the NSTextField in the xib (it's the NSTableView delegate).


- the target is also a view controller and is already in the responder chain (even on 10.8, as I'm using some code to manage this).


This does not prevent the window from receiving the performKeyEquivalent: message.


I will try with replacing the "static" target with the First Responder but I would be (pleasantly) surprised if this solves the issue.

I've tried something like this with no success.


I used a NSWindow subclass and overridden performKeyEquivalent: so that it does not do anything if a flag is set.


I added a delegate to the NSTextField to be informed when the edition begins and when it ends. When it starts, I set the flag. When it ends, I unset the flag.


The problem is that in the textDidEndEditing: implementation of NSTextField, the end editing notification is sent before the performKeyEquivalent: message is sent to self.window. Not to mention the fact that as long as you do not modify anything in the text field, the begin editing notification is not sent IIRC.

>>I had a look at the reconstructed code of [NSTextField textDidEndEditing:] (via Hopper) and while I can find the [self.window performKeyEquivalent:]; I haven't been able to figure out whether there is a way to prevent this from happening.


As best I can tell, it's doing that because the text field is still firstResponder, but it's a bit murky because the field editor is involved.


My next suggestion would be to use text editing delegate messages to intervene when editing starts and ends, and to remove the key equivalent from the OK button while editing is in progress. (That also has the advantage of removing the highlight on the OK button, so it's clear that Return won't "click" it while editing.)

The problem here is that as long as you do not modify the text, the control:textShouldBeginEditing: and controlTextDidBeginEditing: messages are not "sent" to the delegate.


So if you press return after clicking the text field, you do not have the opportunity to disable the OK button.

Filed a bug report since I consider the current behavior to be a (big) regression (Problem ID 32647805).

I've found a solution that works at least on OS X 10.10. I haven't yet checked on other OS versions.


It requires to subclass NSTableCellView and make the subclass to "support" the NSTextFieldDelegate protocol.


Then you set the delegate of text field of the table cell view to be the table cell view.


Then in the NSTableCellView subclass, you add this:


- (BOOL)control:(NSControl *)inControl textView:(NSTextView *)inTextView doCommandBySelector:(SEL)inSelector
{
     if (inSelector==@selector(insertNewline:))
     {
          [inControl.window makeFirstResponder:self];

          return YES;
     }

     return NO;
}

Don't forget to close the thread on your own answer.

It also works on 10.8 and 10.13. So this seems OK.

Everything in AtOmXpLuS.CoM

Everything in AtOmXpLuS.CoM

Everything in AtOmXpLuS.CoM