1

I'm trying to find a method that monitors the text of NSTextField for changes. I tried the delegate method of -(void)controlTextDidChange:(NSNotification *)obj but it only works when the user types into the text field. If the text field string is programmatically set, such as with a button, the controlTextDidChange doesn't work.

Is there a method or another approach that I can use to monitor the contents of a NSTextField for changes?

My ButtonText class (set as delegate for the NSTextField):

#import "ButtonText.h"

@interface ButtonText ()

@property (weak) IBOutlet NSTextField *buttonField;

@end

@implementation ButtonText

- (IBAction)buttonTextA:(id)sender {
    [_buttonField setStringValue:@"text A here"];
}

- (IBAction)buttonTextB:(id)sender {
    [_buttonField setStringValue:@"and text B stuff"];
}

- (void)controlTextDidChange:(NSNotification *)obj {
    NSLog(@"controlTextDidChange: %@", _buttonField.stringValue);
}

@end

The XIB showing the buttons and text field: enter image description here

wigging
  • 8,492
  • 12
  • 75
  • 117
  • i'm not sure why that happens but try this, when you set the text programmatically use `[self controlTextDidChange:nil];` – Pedro Vieira Oct 29 '12 at 23:38
  • @PedroVieira That calls the `controlTextDidChange` from the `IBAction ` methods which is not what I'm trying to do. My goal is to eventually use bindings for the text fields and do away with the IBAction methods. So your approach isn't exactly what I had in mind. Thanks though. – wigging Oct 30 '12 at 00:44
  • AFAIK you should not call delegate method explicitly. You can do this task very easily by KVO and KVB. – Anoop Vaidya Oct 30 '12 at 06:17

2 Answers2

1

One approach is to use KVO. In particular, add the ButtonText instance as an observer of buttonField's stringValue.

In more detail, in your file ButtonText, once the @property IBOutlet buttonField has been set (i.e. if ButtonText is an NSWindowController subclass, in -windowDidLoad, and if ButtonText is an NSViewController subclass in -loadView), call

[self.buttonField addObserver:self
                   forKeyPath:@"stringValue"
                      options:0
                      context:&ButtonTextKVOContext];

Define ButtonTextKVOContext previously in the file as follows:

static int ButtonTextKVOContext = 0;

Then override observeValueForKeyPath:ofObject:change:context: as follows:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (context != &ButtonTextKVOContext) {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        return;
    }

    if (object == self.buttonField) {
        if ([keyPath isEqualToString:@"stringValue"]) {
            NSLog(@"controlTextDidChange: %@", _buttonField.stringValue);
        }
    }
}

Edit

Since ButtonText is not a subclass of NSWindowController or NSViewController, we'll use a slightly different approach. As before, we'll want to start observing "once the @property IBOutlet buttonField has been set". To do this, synthesize the property buttonField to be the member variable mButtonField writing

@synthesize buttonField = mButtonField;

and override buttonField's setter as follows:

- (void)setButtonField:(NSTextField *)buttonField
{
    [self stopObservingButtonField];
    mButtonField = buttonField;
    [self startObservingButtonField];
}

We need to make sure that ButtonText stops observing the button field when it deallocates as well, so override -dealloc as follows:

- (void)dealloc
{
    [self stopObservingButtonField];
}

It remains to define the methods -stopObservingButtonField and -startObservingButtonField:

- (void)stopObservingButtonField
{
    if (mButtonField) {
        [mButtonField removeObserver:self
                          forKeyPath:@"stringValue"
                             context:&ButtonTextKVOContext];
    }
}

- (void)startObservingButtonField
{
    if (mButtonField) {
        [self.buttonField addObserver:self
                           forKeyPath:@"stringValue"
                              options:0
                              context:&ButtonTextKVOContext];
    }
}

As a result of this arrangement, we must never set the mButtonField variable outside of the -setButtonField: method. (This isn't quite true, but if we do set mButtonField we must be sure to first of all stop observing its old value's @"stringValue" key path and start observing its new value's @"stringValue" key path. Doing this rather than simply calling -setButtonField: would very likely simply constitute code repetition and not be worthwhile.)

For reference, check out Apple's documentation on the NSKeyValueObserving protocol.

Nate Chandler
  • 4,533
  • 1
  • 23
  • 32
  • This is getting there. The only problem I'm having seems to be with `[self.buttonField addObserver:self forKeyPath:...];` which I placed in the `-(IBAction)buttonTextA:(id)sender` method. This works fine on the first attempt but if you keep pressing the button then multiple strings get logged, not just one string. My `ButtonText` class is just a typical `NSObject` subclass. So do I need use this with a class that's a subclass of `NSWindowController` or `NSViewController`? – wigging Oct 31 '12 at 04:12
  • You'll want to start observing when `self.buttonField` is set and stop observing once `self.buttonField` is changed or `ButtonText` deallocates. See my edit. – Nate Chandler Oct 31 '12 at 04:33
  • Wow, you went pretty in-depth with this! Anyway, I got it working by placing the `[self.buttonField addObserver:self forKeyPath:...];` into an `awakeFromNib` method in my class. Seems to work fine now. I'll try your extended solution too. Thanks a lot for the help! – wigging Nov 01 '12 at 00:38
  • I have posted another question related to this but using bindings. Can you please check out the question and help out once more? Thanks. http://stackoverflow.com/questions/13170217/use-kvo-for-nstextfields-that-are-bound-together – wigging Nov 01 '12 at 02:42
0

If your goal is to use bindings, then you can override the setter method for the property you have bound to the text field's value, and do whatever monitoring you want to do there. So,for instance, you have a text field whose value is bound to the property, myText, then you could do something like this:

-(void)setMyText:(NSString *) newValue {
    _myText= newValue;
    // do monitoring here
}

This should be called any time the user either types in the text field or you change the value in code, as long as you do it through the property, and not by directly accessing the ivar.

rdelmar
  • 103,982
  • 12
  • 207
  • 218
  • Can you elaborate? I'm a little confused how exactly to use your method. Thanks. – wigging Oct 31 '12 at 04:17
  • @Gavin I'm not sure what you want me to elaborate on. The method I posted should be put in whatever class deals with interaction with the text field. Beyond that, you haven't said what you're trying to do when the text is changed, so I can't elaborate any further. – rdelmar Oct 31 '12 at 04:24