ControlTextDidChange not working for setting string of NSTextField - objective-c

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

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.

Related

NSTreeController KVO notifications unexpectedly not firing

I've encountered a bit of a poser involving NSTreeController and KVO. NSTreeController's selectionIndexPaths property is documented as being KVO-observable—and when I observe it directly, it works perfectly. However, if I list NSTreeController's selectionIndexPath as a dependency of some other property, and then try to observe that, the notifications are not fired when one would expect.
Here's the shortest sample code I could come up with to demonstrate what I mean:
import Cocoa
class ViewController: NSViewController {
// Our tree controller
#IBOutlet dynamic var treeController: NSTreeController!
// Some random property on my object; you'll see why it's here later
#objc dynamic var foo: String = "Foo"
// A quick-and-dirty class to give us something to populate our tree with
class Thingy: NSObject {
#objc let name: String
init(_ name: String) { self.name = name }
#objc var children: [Thingy] { return [] }
}
// The property that the tree controller's `Content Array` is bound to
#objc dynamic var thingies: [Thingy] = [Thingy("Foo"), Thingy("Bar")]
// Dependencies for selectionIndexPaths
#objc private static let keyPathsForValuesAffectingSelectionIndexPaths: Set<String> = [
#keyPath(treeController.selectionIndexPaths),
#keyPath(foo)
]
// This property should be dependent on the tree controller's selectionIndexPaths
// (and also on foo)
#objc dynamic var selectionIndexPaths: [IndexPath] {
return self.treeController.selectionIndexPaths
}
// Some properties to store our KVO observations
var observer1: NSKeyValueObservation? = nil
var observer2: NSKeyValueObservation? = nil
// And set up the observations
override func viewDidLoad() {
super.viewDidLoad()
self.observer1 = self.observe(\.selectionIndexPaths) { _, _ in
print("This is only logged when foo changes")
}
self.observer2 = self.observe(\.treeController.selectionIndexPaths) { _, _ in
print("This, however, is logged when the tree controller's selection changes")
}
}
// A button is wired to this; its purpose is to set off the
// KVO notifications for foo
#IBAction func changeFoo(_: Any?) {
self.foo = "Bar"
}
}
In addition, the following setup is done in the storyboard:
Add a tree controller, and connect the view controller's treeController outlet to it.
Bind the tree controller's "Content Array" binding to thingies on the view controller.
Set the tree controller's "Children Key Path" to children.
Create an outline view, and bind its "Content" and "Selection Index Paths" bindings to arrangedObjects and selectionIndexPaths respectively on the tree controller.
Create a button, and point it at the view controller's changeFoo: method.
If you'd like to try it yourself, I've uploaded a sample project here.
The behavior is as follows:
The notification for observer2 is always fired whenever the outline view's (and thus the tree controller's) selection changes, as one would expect.
However, the notification for observer1 is not fired when the outline view's selection changes.
However, observer1's notification is fired when the button is clicked, and foo is changed. This suggests that the property's dependencies are being considered, but just not for this one particular key path.
Using the old-school method with an observeValue(forKeyPath:bla:bla:bla:) override instead of the swank Swift 4 closure-based system seems to behave the same way.
EDIT: Well, it's not Swift's fault! Same thing happens when I write this program in Objective-C:
#interface Thingy: NSObject
#property (nonatomic, copy) NSString *name;
- (instancetype)initWithName:(NSString *)name;
#end
#implementation Thingy
- (instancetype)initWithName:(NSString *)name {
self = [super init];
if (self == nil) {
return nil;
}
self->_name = name;
return self;
}
- (NSArray *)children { return #[]; }
#end
void *ctxt1 = &ctxt1;
void *ctxt2 = &ctxt2;
#interface ViewController()
#property (nonatomic, strong) IBOutlet NSTreeController *treeController;
#property (nonatomic, copy) NSString *foo;
#property (nonatomic, copy) NSArray *thingies;
#end
#implementation ViewController
+ (NSSet *)keyPathsForValuesAffectingSelectionIndexPaths {
return [NSSet setWithObjects:#"treeController.selectionIndexPaths", #"foo", nil];
}
- (NSArray *)selectionIndexPaths {
return self.treeController.selectionIndexPaths;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.thingies = #[[[Thingy alloc] initWithName:#"Foo"], [[Thingy alloc] initWithName:#"Bar"]];
[self addObserver:self forKeyPath:#"selectionIndexPaths" options:0 context:ctxt1];
[self addObserver:self forKeyPath:#"treeController.selectionIndexPaths" options:0 context:ctxt2];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if (context == ctxt1) {
NSLog(#"This only gets logged when I click the button");
} else if (context == ctxt2) {
NSLog(#"This gets logged whenever the selection changes");
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (IBAction)changeFoo:(__unused id)sender {
self.foo = #"Bar";
}
#end
I've been staring at this for a while, and I cannot figure out why directly observing treeController.selectionIndexPaths works, but observing a property that depends on treeController.selectionIndexPaths does not. And since I've generally felt like I had a pretty good handle on KVO and its workings, it is really bugging me that I can't explain this.
Does anyone know the reason for this discrepancy?
Thanks!

Xcode OSX, bind NSString to UILabel

I have a Model Class like this:
Header:
#interface RTSecurityModel : NSObject
{
NSString *code;
}
#property NSString *code;
#end
Implementation:
#implementation RTSecurityModel
#synthesize code;
#end
Then I have my App Delegate:
Header:
#interface RTAppDelegate : NSObject <NSApplicationDelegate>
{
RTSecurityModel *security;
}
#property (assign) IBOutlet NSWindow *window;
#property RTSecurityModel *security;
#end
Implementation:
#implementation RTAppDelegate
#synthesize security;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
security = [[RTSecurityModel alloc] init];
security.code = #"test";
}
Then in my MainMenu.xib I've create a label and in the Bindings Inspector set "Bind To: App Delegate" with "Model Key Path: security.code".
But nothing is showing when I'm starting my application.
I tried soooo many ways to bind this variable, but no one gave success.
Please help me not to hate XCode and Cocoa!
UPD: http://www.experts-exchange.com/Programming/Languages/C/A_3381-Simple-Binding-Cocoa-GUI-Application-without-Outlets.html
Here is the sample how to set Property and Label value by editing the Text Field
But is there a way to edit Label without editing the Text Field? Or without Text Field at all?
UPD2:
You must not create another instance of Object
security = [[RTSecurityModel alloc] init]; // Kill this
Many many thanks to Viktor Lexington
Instead of using security.code as the model path use code. Use the class RTSecurityModel in the value section of the bindings tab instead of the AppDelegate.
Here is a demo project.
Do not bind the Text Field Cell, use the Text Field.
You can check if a value is null if you fill the Null Placeholder with text, will it show that text instead? Then in time of binding the value it null.
To see your RTSecurityModel in the Interface Builder you must let it know your class, it won't look for it.
Add an Object and then set the custom class of it to RTSecurityModel.
Then you can choose this object and set the referencing outlet to the property in the App Delegate.
Assignment will now be directly reflected in the label.
I can think of two ways to solve this programmatically without Interface Builder:
Key Value Coding
// add an observer for the value on the object that has the method below implemented
[self addObserver: self forKeyPath: #"security.code" options: NSKeyValueObservingOptionNew context: NULL];
// method will be called when the observer has 'seen' a value change
-(void) observeValueForKeyPath: (NSString *)keyPath ofObject: (id) object change: (NSDictionary *) change context: (void *) context {
label.text = ...
}
Use a custom setter for code (#synthesize will still create the getter for you)
- (void)setCode:(NSString *)aString {
label.text = aString;
}

How to observe (KVO) count change in an NSMutableArray [duplicate]

I'd like to be notified, when the count, ie. number of items in an NSArray changes..
Of course I wouldn't need this, if I was in control of addition and removal of objects into the array. But I am not, it happens unpredictably with regards to Business Process Model and depends on external factors.
Is there some simple elegant solution?
EDIT: I am correcting this to NSMutableArray of course..
You’ll need to use KVC. But how to go about doing it? After all, NSMutableArray is not Key-Value-Coding compliant for its mutation methods or contents changes. The answer is proxying –as subclassing NS[Mutable]Array is far too much of a hassle.
NSProxy is a great little class that you can use to intercept the messages sent to your array as though you were an NSMutableArray, then forward them on to some internal instance. Unfortunately, it is also not KVC compliant, as the guts of KVC live in NSObject. We’ll have to use that, then. A sample interface might look something like this:
#interface CFIKVCMutableArrayProxy : NSObject {
NSMutableArray *_innerArray;
}
- (NSUInteger)count;
- (void)insertObject:(id)anObject atIndex:(NSUInteger)index;
- (void)removeObjectAtIndex:(NSUInteger)index;
- (void)addObject:(id)anObject;
- (void)removeLastObject;
- (void)insertObjects:(NSArray *)objects atIndexes:(NSIndexSet *)indexes;
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject;
//…
#end
As you can see, we’re simulating an interface for NSMutableArray, which is necessary, as our proxy should implement everything as though it were an NSMutableArray. This also makes the implementation as simple as possible, as we can just forward the selectors on to our inner NSMutableArray pointer. For the sake of brevity, I’ll only implement two methods to show you what a general outline looks like:
#implementation CFIKVCMutableArrayProxy
//…
- (NSUInteger)count {
return _innerArray.count;
}
- (void)addObject:(id)anObject {
[self willChangeValueForKey:#"count"];
[_innerArray addObject:anObject];
[self didChangeValueForKey:#"count"];
}
- (void)removeLastObject {
[self willChangeValueForKey:#"count"];
[_innerArray removeLastObject];
[self didChangeValueForKey:#"count"];
}
#end
If you have no opportunities to wrap an array like this, then try to re-think your code. If an external dependency is forcing you into this kind of corner, try to remove it. It’s always a bad thing to work around your own tools.
To observe changes in a mutableArray one needs to use mutable proxy object given by
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key
which is KVO compliant, i.e. any change of proxy object sends will/did change notifications.
The following demo class shown the full implementation
#interface DemoClass : NSObject
#property (nonatomic) NSMutableArray *items;
- (void)addItemsObserver:(id)object;
- (void)removeItemsObserver:(id)object;
#end
#implementation DemoClass
- (NSMutableArray *)items;
{
return [self mutableArrayValueForKey:#"_items"];
}
- (void)addItemsObserver:(id)object
{
[self addObserver:object forKeyPath:#"_items.#count" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
}
- (void)removeItemsObserver:(id)object
{
[self removeObserver:object forKeyPath:#"_items.#count" context:nil];
}
#end
#interface ObservingClass : NSObject
#property (nonatomic) DemoClass *demoObject;
#end
#implementation ObservingClass
- (instanstype)init
{
if (self = [super init]) {
_demoObject = [DemoClass new];
[_demoObject addItemsObserver:self];
}
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
NSLog(#"is called on demoObject.items.count change");
}
- (void)dealloc
{
[_demoObject removeItemsObserver:self];
}
#end
Now every time you add or remove an object in the items you'll see new log in console (observeValueForKeyPath is called).
Any direct change of auto-synthesised ivar _itemsarray will have no effect.
Also note that you strongly need to set the observer on _items.#count (observing items.#count is senseless).
Note that you needn't to init _items or self.items. It will be done behind the scene when you call items getter.
Every time you change the "array" items you will get new object _items with new address. But I can still find it via items proxy getter.

KVO with Run-to-Completion semantics - Is it possible?

I recently ran into reentrancy issues with KVO. To visualize the problem, I would like to show a minimal example. Consider the interface of an AppDelegate class
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (nonatomic) int x;
#end
as well as its implementation
#implementation AppDelegate
- (BOOL) application:(__unused UIApplication *)application
didFinishLaunchingWithOptions:(__unused NSDictionary *)launchOptions
{
__unused BigBugSource *b = [[BigBugSource alloc] initWithAppDelegate:self];
self.x = 42;
NSLog(#"%d", self.x);
return YES;
}
#end
Unexpectedly, this program prints 43 to the console.
Here's why:
#interface BigBugSource : NSObject {
AppDelegate *appDelegate;
}
#end
#implementation BigBugSource
- (id)initWithAppDelegate:(AppDelegate *)anAppDelegate
{
self = [super init];
if (self) {
appDelegate = anAppDelegate;
[anAppDelegate addObserver:self
forKeyPath:#"x"
options:NSKeyValueObservingOptionNew
context:nil];
}
return self;
}
- (void)dealloc
{
[appDelegate removeObserver:self forKeyPath:#"x"];
}
- (void)observeValueForKeyPath:(__unused NSString *)keyPath
ofObject:(__unused id)object
change:(__unused NSDictionary *)change
context:(__unused void *)context
{
if (appDelegate.x == 42) {
appDelegate.x++;
}
}
#end
As you see, some different class (that may be in third-party code you do not have access to) may register an invisible observer to a property. This observer is then called synchronously, whenever the property's value has changed.
Because the call happens during the execution of another function, this introduces all sort of concurrency / multithreading bugs although the program runs on a single thread. Worse, the change happens without an explicit notice in the client-code (OK, you could expect that concurrency issues arise whenever you set a property...).
What is the best practice to solve this problem in Objective-C?
Is there some common solution to regain run-to-completion semantics automatically, meaning that KVO-Observation messages go through an event-queue, AFTER the current method finishes executing and invariants / postconditions are restored?
Not exposing any properties?
Guarding every critical function of an object with a boolean variable to ensure that reentrancy is not possible?
For example: assert(!opInProgress); opInProgress = YES; at the beginning of the methods, and opInProgress = NO; at the end of the methods. This would at least reveal those kind of bugs directly during runtime.
Or is it possible to opt out of KVO somehow?
Update
Based on the answer by CRD, here is the updated code:
BigBugSource
- (void)observeValueForKeyPath:(__unused NSString *)keyPath
ofObject:(__unused id)object
change:(__unused NSDictionary *)change
context:(__unused void *)context
{
if (appDelegate.x == 42) {
[appDelegate willChangeValueForKey:#"x"]; // << Easily forgotten
appDelegate.x++; // Also requires knowledge of
[appDelegate didChangeValueForKey:#"x"]; // whether or not appDelegate
} // has automatic notifications
}
AppDelegate
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
if ([key isEqualToString:#"x"]) {
return NO;
} else {
return [super automaticallyNotifiesObserversForKey:key];
}
}
- (BOOL) application:(__unused UIApplication *)application
didFinishLaunchingWithOptions:(__unused NSDictionary *)launchOptions
{
__unused BigBugSource *b = [[BigBugSource alloc] initWithAppDelegate:self];
[self willChangeValueForKey:#"x"];
self.x = 42;
NSLog(#"%d", self.x); // now prints 42 correctly
[self didChangeValueForKey:#"x"];
NSLog(#"%d", self.x); // prints 43, that's ok because one can assume that
// state changes after a "didChangeValueForKey"
return YES;
}
What you are asking for is manual change notification and is supported by KVO. It is a three stage process:
Your class overrides + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey returning NO for any property you wish to defer notifications for and deferring to super otherwise;
Before changing a property you call [self willChangeValueForKey:key]; and
When you are ready for the notification to occur you call [self didChangeValueForKey:key]
You can build on this protocol quite easily, e.g. it is easy to keep a record of keys you have changed and trigger them all before you exit.
You can also use willChangeValueForKey: and didChangeValueForKey with automatic notifications turned on if you directly alter the backing variable of a property and need to trigger KVO.
The process along with an examples is described in Apple's documentation.

What is best practice to interaction with object in objective-c?

My questions is next:
For example I have object A (this is data model object). Assume that object A have some property (for example request property). Also I have object B (this is my view object).
So my problem is next: when my data model will be changed (the value for request property changed) I want to know about this events in my view (object B)
How to create this interaction between object.
For example in request is written to "some_value" and after this object B immediately know about it.
Thanks for response!
You can use delegation pattern, NSNotifications, callback blocks and even KVO. Choice depends on situation, in your case delegate or callback block would work.
I would use Key Value Observing. Your view controller (not the view itself) would set itself up as an observer for the data model object and when it gets observer notifications, it would update the view.
[myDataObject addObserver: myViewController
forKeyPath: #"request"
options: NSKeyValueObservingOptionNew
context: nil];
// in the view controller you need
-(void) observeValueForKeyPath: (NSString*) path
ofObject: (id) aDataObject
change: (NSDictionary*) changeDictionary
context: (void*) context]
{
if (aDataObject == myDataObject
&& [path isEqualToString: #"request"])
{
// change you are interested in
}
// Call suoer implementation of this method if it implements it
}
Don't forget to remove the observer when you are done with it.
Also, be careful in a threaded environment. Observations are notified on the same thread that the change happens on. If this is not the main thread, you'll need to use -performSelectorOnMainThread:withObject:waitUntilDone: to make any changes to the UI.
If you just want object B to know whats up I would suggest using delegation.
If maybe later you want object C, D and E to know too what happend in object A i would suggest using NSNotification.
For example I have class DataModel. In this step I add observer for my property str. For object I will send my view controller.
.h
#import <Foundation/Foundation.h>
#interface DataModel : NSObject
#property (strong, nonatomic) NSString *str;
- (void)setUpObserver:(id)object;
#end
.m
#import "DataModel.h"
#implementation DataModel
#synthesize str;
- (void)setUpObserver:(id)object
{
[self addObserver:object forKeyPath: #"str" options: NSKeyValueObservingOptionNew context: nil];
}
#end
In my view controller
#import "DataModel.h"
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
dm = [[DataModel alloc] init];
[dm setUpObserver:self];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if (object == dm && [keyPath isEqualToString: #"str"])
{
NSLog(#"it's work");
}
}
- (IBAction)changeValue:(id)sender {
dm.str = #"test change value";
}
#end
This is my realization of KVO. Thanks JeremyP for explanation.