NSResponder resignFirstResponder fires on wrong object

652 views Asked by At

I have a Mac application (SDK 10.10) with some NSTextFields:

Xib file

Since I need to get notified when a text field gets and resigns focus, I subclassed NSTextField:

@interface MyTextField : NSTextField
@end

@implementation MyTextField

- (BOOL)becomeFirstResponder
{
    BOOL didBecomeFirstResponder = [super becomeFirstResponder];
    NSLog(@"%@ didBecomeFirstResponder = %@", [self accessibilityLabel], didBecomeFirstResponder?@"YES":@"NO");
    return didBecomeFirstResponder;
}

- (BOOL)resignFirstResponder
{
    BOOL didResignFirstResponder = [super resignFirstResponder];
    NSLog(@"%@ didResignFirstResponder = %@", [self accessibilityLabel], didResignFirstResponder?@"YES":@"NO");
    return didResignFirstResponder;
}

@end

When runing this code and tabbing between the 3 text fields, I get this output in the console:

firstField didResignFirstResponder = YES
firstField didBecomeFirstResponder = YES
secondField didResignFirstResponder = YES
secondField didBecomeFirstResponder = YES
thirdField didResignFirstResponder = YES
thirdField didBecomeFirstResponder = YES
firstField didResignFirstResponder = YES
firstField didBecomeFirstResponder = YES
secondField didResignFirstResponder = YES
secondField didBecomeFirstResponder = YES

Every time I hit the TAB key (or click in one of the inactive text fields), the app outputs

<new first responder> didResignFirstResponder = YES
<new first responder> didBecomeFirstResponder = YES

Shouldn't that be

<old first responder> didResignFirstResponder = YES
<new first responder> didBecomeFirstResponder = YES

???

Do I something terribly wrong here?

The documentation of - (BOOL)resignFirstResponder says

Notifies the receiver that it’s been asked to relinquish its status as first responder in its window.

So why gets resignFirstResponder called on the new first responder and not the old one?

2

There are 2 answers

0
Michał Pałys-Dudek On

I had the same issue and couldn't find a solution, so I ended up with working around it and using textDidEndEditing() method for the same purpose. Swift code:

MyTextField subclasses NSTextField and adds myDelegate property to it that should handle logic after the text field lost focus.

class MyTextField : NSTextField
{

/// Custom delegate with other methods than NSTextFieldDelegate.
var myDelegate: MyTextFieldDelegate? = nil;

override func becomeFirstResponder() -> Bool {
    let became = super.becomeFirstResponder();
    if (became) {
        self.myDelegate?.myTextFieldDidBecomeFirstResponder(self);
    }
    return became;
}

override func resignFirstResponder() -> Bool {
    let resigned = super.resignFirstResponder();
    if (resigned) {
        self.myDelegate?.myTextFieldDidResignFirstResponder(self);
    }
    return resigned;
}

override func textDidEndEditing(obj: NSNotification) {
    super.textDidEndEditing(obj);
    self.myDelegate?.myTextFieldDidResignFirstResponder(self);
}

}

The protocol itself:

protocol MyTextFieldDelegate
{

    func myTextFieldDidBecomeFirstResponder(textField: MyTextField);

    func myTextFieldDidResignFirstResponder(textField: MyTextField);
}

Obviously, instead of using and calling a delegate, you could just implement your lost/gained focus logic right there (e.g. draw different border, etc).

The clue is the textDidEndEditing() method and it seems to work for fine when I click in another field, when I press enter or when I tab out.

0
aleclarson On

Someone on the Apple developer forums explained why it behaves like it does:

When text fields have focus, they aren't actually the first responder. Cocoa creates a special text view, called the field editor, and temporarily adds it to the view hierarchy, covering the text field. It makes that text view the first responder. This text view is what handles all user interaction.

Source: https://forums.developer.apple.com/thread/109158#339676

 

Workarounds

Overriding the textDidEndEditing: method (mentioned by Michał in this answer) is the best solution, IMO.

If you don't want to subclass NSTextField, create a class that conforms to the NSTextFieldDelegate protocol and implements the controlTextDidEndEditing: method. Then, set the delegate property of your NSTextField instance.

Lastly, you could override the fieldEditor:forObject: method of an NSWindow subclass. But there's literally no formal documentation for this approach (that I could find).

To learn more about the "field editor", read these articles: