Monitoring Key Values ​​on NSTextFields
In your implementation of the -awakeFromNib method -awakeFromNib you wrote
[self.fieldA addObserver:self forKeyPath:@"numA" options:0 context:&MainClassKVOContext];
This does not mean that you hope: self.fieldA not compatible with key values for the numA key: if you try to send -valueForKey: or -setValue:forKey: using the @"numA" key to self.fieldA , you will get the following exceptions:
[valueForUndefinedKey:]: this class is not the key to encode for the numA key.
and
[setValue: forUndefinedKey:]: this class is not suitable for encoding the key for the numA key.
As a result , NSTextField instances are not a key compliance for @"numA" , either: the first requirement to be KVO-compatible for some key must be KVC-compatible for this key.
It is, however, KVO-compatible, among other things, stringValue . This allows you to do what I described earlier .
Note None of this has changed the way you configured the bindings in Interface Builder. More on this later.
Problem with monitoring key values ​​on an NSTextField stringValue
Observing the NSTextField value for @"stringValue" works when -setStringValue: is called on an NSTextField . This is the result of internal KVO operations.
A short trip to the internal KVO
When you start observing a key value by observing an object for the first time, the class of the object changes — its pointer isa changes. This can be seen by overriding -addObserver:forKeyPath:options:context:
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context { NSLog(@"%p, %@", self->isa, NSStringFromClass(self->isa)); [super addObserver:observer forKeyPath:keyPath options:options context:context]; NSLog(@"%p, %@", self->isa, NSStringFromClass(self->isa)); }
In general, the class name changes from Object to NSKVONotifying_Object .
If we called -addObserver:forKeyPath:options:context: in an Object instance with the key @"property" - the key for which Object instances are KVC compatible - the next time -setProperty: call -setProperty: on, our Object instance (in fact, now an NSKVONotifying_Object instance ), the following messages will be sent to the object
-willChangeValueForKey: pass @"property"-setProperty: pass @"property"-didChangeValueForKey: pass @"property"
Violation of any of these methods indicates that they are called from the undocumented function _NSSetObjectValueAndNotify .
The relevance of all this is that the -observeValueForKeyPath:ofObject:change:context: method is called on the observer that we added to our Object instance for the key path @"property" from -didChangeValueForKey: Here's the top of the stack trace:
-[Observer observeValueForKeyPath:ofObject:change:context:] NSKeyValueNotifyObserver () NSKeyValueDidChange () -[NSObject(NSKeyValueObserverNotification) didChangeValueForKey:] ()
How does this relate to NSTextField and @"stringValue" ?
In the previous setup, you added an observer to the text box on -awakeFromNib . This meant that your text field was already an instance of NSKVONotifying_NSTextField .
Then you press one button or another, which in turn will call -setStringValue into the text box. You were able to observe this change because - as an instance of NSKVONotifying_NSTextField - your text field, actually setStringValue:value
willChangeValueForKey:@"stringValue"setStringValue:valuedidChangeValueForKey:@"stringValue"
As stated above, internally didChangeValueForKey:@"stringValue" all objects that observe the text field value for @"stringValue" are notified that the value for this key has changed in their own implementations -observeValueForKeyPath:ofObject:change:context: In particular, this is true for the object that you added as an observer for the text field in -awakeFromNib .
So, you were able to observe the change in the text field value for @"stringValue" , because you added yourself as a text field observer for this key and because -setStringValue was called in the text field .
So what is the problem?
So far, under the guise of a discussion of "Problems with observing key values ​​on NSTextFields," we have only actually understood the initial sentence
Observing the NSTextField value for @"stringValue" works when -setStringValue: is called on an NSTextField .
And that's great! So what is the problem?
The problem is that -setStringValue: not called in the text field when the user enters OR even after the user has finished editing (for example, by going from the text field). (In addition, -willChangeValueForKey: and -didChangeValueForKey: not called manually. If they were, our KVO would work, but it isn’t.) This means that although our KVO on @"stringValue" works when -setStringValue: It is called on a text field, it does NOT work when the user enters text.
TL; DR : KVO on @"stringValue" NSTextField not good enough as it does not work for user input.
Binding an NSTextField Value to a String
Try using bindings.
Initial setup
Create a sample project with a separate window controller (I used the WindowController declaration WindowController ) complete with XIB. ( Here's the project I'm starting with GitHub. ) In WindowController.m , the stringA property has been stringA to the class extension:
@interface WindowController () @property (nonatomic) NSString *stringA; @end
In Interface Builder, create a text box and open the binding inspector:

In the Value heading, expand the Value element:

In the button that appears next to the “Bind” field, the checkbox “Custom default controller” is selected. We want to bind the value of the text field to our instance of WindowController , so choose File Owner instead. When this happens, the "Controller Key" field will be emptied, and the "Model Key Path" field will be changed to "self".

We want to bind this text field value to our WindowController property of WindowController instance to change the "model key path" to self.stringA :

This is where we are done. ( Progress so far on GitHub. ) We have successfully bound the value of the text field to our WindowController stringA property.
Testing
If we set stringA to some value in -init, this value will be displayed in the text box when the window loads:
- (id)init { self = [super initWithWindowNibName:@"WindowController"]; if (self) { self.stringA = @"hello world"; } return self; }

And we have already set the bindings in the other direction; after editing, the property of our window controller stringA set in the text field. We can verify this by overriding its setter:
- (void)setStringA:(NSString *)stringA { NSLog(@"%s: stringA: <<%@>> => <<%@>>", __PRETTY_FUNCTION__, _stringA, stringA); _stringA = stringA; }
Reply Hazy, Try Again
After entering some text in the text box and clicking the tab, we will see a printout
-[WindowController setStringA:]: stringA: <<(null)>> => <<some text>>
It looks great. Why didn’t we talk about this? Here's a bit of a hitch: a persistent pressed tab . Binding the value of a text field to a line does not set the value of the line until editing ends in the text field.
New Hope
However, there is hope! The Cocoa Binding Documentation for NSTextField states that one NSContinuouslyUpdatesValueBindingOption binding option is available for the NSTextField parameter. And now, there is a flag corresponding to this very option in the Bindings Inspector for the value of NSTextField. Go and check the box.

With this change in place, when we make the change, the update for the stringA window controller stringA constantly logs out:
-[WindowController setStringA:]: stringA: <<(null)>> => <<t>> -[WindowController setStringA:]: stringA: <<t>> => <<th>> -[WindowController setStringA:]: stringA: <<th>> => <<thi>> -[WindowController setStringA:]: stringA: <<thi>> => <<thin>> -[WindowController setStringA:]: stringA: <<thin>> => <<thing>> -[WindowController setStringA:]: stringA: <<thing>> => <<things>> -[WindowController setStringA:]: stringA: <<things>> => <<things >> -[WindowController setStringA:]: stringA: <<things >> => <<things i>> -[WindowController setStringA:]: stringA: <<things i>> => <<things in>>
Finally, we constantly update the window controller line from the text box. The rest is easy. As a quick proof of concept, add a few more text fields to the window, bind them to stringA and configure them to be constantly updated. You currently have three synchronized NSTextField s! Here is a project with three synchronized text fields.
Rest of the way
You want to customize three text fields displaying numbers that have something to do with each other. Since we are dealing with numbers now, we will remove the stringA property from WindowController and replace it with numberA , numberB and numberC :
@interface WindowController () @property (nonatomic) NSNumber *numberA; @property (nonatomic) NSNumber *numberB; @property (nonatomic) NSNumber *numberC; @end
Then we associate the first text field with number A with the file owner, the second with number B, etc. Finally, we just need to add a property that represents the quantity that is represented in these various ways. Let me call this value quantity .
@interface WindowController () @property (nonatomic) NSNumber *quantity; @property (nonatomic) NSNumber *numberA; @property (nonatomic) NSNumber *numberB; @property (nonatomic) NSNumber *numberC; @end
We will need constant conversion factors to convert from quantity units to numberA units, etc., so add
static float convertToA = 1000.0f; static float convertToB = 573.0f; static float convertToC = 720.0f;
(Of course, use numbers that are relevant to your situation.) With this help, we can implement accessors for each of the numbers:
- (NSNumber *)numberA { return [NSNumber numberWithFloat:self.quantity.floatValue * convertToA]; } - (void)setNumberA:(NSNumber *)numberA { self.quantity = [NSNumber numberWithFloat:numberA.floatValue * 1.0f/convertToA]; } - (NSNumber *)numberB { return [NSNumber numberWithFloat:self.quantity.floatValue * convertToB]; } - (void)setNumberB:(NSNumber *)numberB { self.quantity = [NSNumber numberWithFloat:numberB.floatValue * 1.0f/convertToB]; } - (NSNumber *)numberC { return [NSNumber numberWithFloat:self.quantity.floatValue * convertToC]; } - (void)setNumberC:(NSNumber *)numberC { self.quantity = [NSNumber numberWithFloat:numberC.floatValue * 1.0f/convertToC]; }
All of the various number accessors are now just indirect mechanisms for accessing quantity and are ideal for bindings. There is only one more thing left: we need to make sure that observers repeat all numbers with every change in quantity :
+ (NSSet *)keyPathsForValuesAffectingNumberA { return [NSSet setWithObject:@"quantity"]; } + (NSSet *)keyPathsForValuesAffectingNumberB { return [NSSet setWithObject:@"quantity"]; } + (NSSet *)keyPathsForValuesAffectingNumberC { return [NSSet setWithObject:@"quantity"]; }
Now, whenever you enter text in one of the text fields, the rest is updated accordingly. Here's the final version of the project on GitHub .