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:
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);
}
}
}
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.