Kvo/kvc

Hello,
Just to be sure: I have two properties, I want one of them to be set if the other changes, but not the inverse.

I have some application preferences and some document preferences. When the user opens a new document, its preferences are set to the application ones, but he’s free to change them after that. But if the application preferences are changes, the document ones are changed too (as well as future docs).

The preferences of the application will persist via the Shared User Controller, the document ones with the document.

Anyway: What I want is to add a observer (the document properties will observe the application properties). Questions:

  1. If an application-range textfield is changed, will the doc-range textfield change? (both are bound by value to a property)
  2. The observance is not mirrored?

Thank you for your help and patience!

OK, I solved this so:

This works (a field copies the content of the other, but NOT the reverse). Of course if I set the same property to both fields value, the reverse change occurs.

Now, this works for a one-to-one example. I have to bind several fields, so I’ll have to check which field has post, ant then which “linked” field to update.

Just can’t make this thing work.

Look at this. I expected a single character to be copied from one text field to another and I got all that for the same price :

a key/value observer is supposed to be handled with the

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

method, not with didChangeValueForKey

and don’t forget to remove the observer in the dealloc method

Thank you very much Stefan.

I’m very new to this KVO trick. I hope that next month I’ll have a complete array of custom objects observing each other and reacting accordingly (an old dream in procedural Pascal).

I’m working with ARC. Is it of concern?

kvo/kvc is hard to understand just by trial & error
I recommend to read
Key-Value Coding Programming Guide
Key-Value Observing Programming Guide

It’s worse than you think. I HAVE read these guides. :stuck_out_tongue:

I have another issue. As you see, I put the new value of the first field into a second text field. But when I type, I get this:

2012-05-25 19:00:46.645 null[13563:403] *** Assertion failure in -[NSTextFieldCell _objectValue:forString:errorDescription:], /SourceCache/AppKit/AppKit-1138.47/AppKit.subproj/NSCell.m:1564
2012-05-25 19:00:46.646 null[13563:403] Exception detected while handling key input.
2012-05-25 19:00:46.646 null[13563:403] Invalid parameter not satisfying: aString != nil

You can catch and handle this case in the delegate method

I did. It works. What a relief. Thanks again.

And about ARC?

I don’t know, whether ARC removes observers automatically.
In the app delegate it’s no problem to omit it because everything is deallocated when the app quits,
but in custom classes it could cause memory leaks or at least unexpected behavior.

Personally I don’t use ARC because I want to have control about memory management.
Probably it’s just habit. I “grow up” with memory management and as iOS doesn’t support garbage collection
I’ve never used GC too

ARC is about memory management; removing KVO observers isn’t really a memory issue. So yes, you keep doing it as before. If you do it in dealloc, just make sure you don’t call [super dealloc].

I had one problem, found its solution and just wanted to share it.

My user can choose between two way of showing some data items (both are controls in a window’s toolbar):

– a segmented control, when there are only a few items;
– a pop menu when the segmented control becomes too big for the size of the window.

I had decided to put this choice into the preferences, so he/she has not to reset the control each time. So I put a checkbox saying “Items as pop menu”. When the application is launched, it looks for this property and shows the correct control.

So far, so good.

But of course, if the user wants to change the control, the toolbar has to reflect the change in real time, not at next launch. And this became interesting.

If I set the checkbox to call an IBAction, the bound property (the preference) will not be updated. I could look at the checkbox value and set the property into the handler. Really, really ugly.

In order not to dispatch the code, I want the application to be able to call this handler-- but if the application is the sender, I can’t check it value! So I have to make an instance method of it, not an IBAction. Sad.

No way to bind the hidden flag of both controls to the checkbox value: they are in a tool bar, I don’t want the controls to appear or disappear, I want one to replace the other. No good, no good.

The solution is: using KVO.

The application launches, it tests the preference property and install the correct control into the tool bar, as before:

Now, when the user click on the check box, it does not trigger an IBAction: it just sets the bound preference value. But this value is observed (udc is the UserDefaultController):

So when the value changes, the application is aware of it and install the control, using the same routine as before:

For info, that’s what this installClassSelector method does (accessing toolbar items is made by identifiers):

And as supplementary info, the segmented control and the menu item are synchronized: the objects just say each other “Hey, my selection has changed, this is the new index, set yours”. Anytime when the user changes the control, they show the same selection.

These lines are mainly for StefanK, who pointed the KVO/KVC advantages (I wanted to solve everything using bindings, I’m really fond of them, but as rdelmar said “they’re not the answer to everything”).

I hope this code may be useful. So I won’t be just complaining about problems on this site.

two small notes:

To retrieve a value for a given key from a dictionary use objectForKey: rather than valueForKey:
In most cases both methods do the same, but for example you will get unexpected behavior if the key contains a dot, because valueForKey: treats the dot as keypath delimiter.

To retrieve primitive values from user defaults this could be more convenient than accessing the controller object

NSInteger integerValue = [[NSUserDefaults standardUserDefaults] integerForKey:@"someKey"]