Quite often I encounter a scenario when I want to observe changes on a retained property:
#interface AnObserver {…}
#property(retain) Foo *foo;
Now when I want to set up the observing, I need to write my own setter, repeating all the boilerplate setter code:
- (void) setFoo: (Foo*) newFoo {
if (newFoo == foo)
return;
[foo removeObserver:self forKeyPath:…];
[foo release], foo = [newFoo retain];
[foo addObserver:self forKeyPath:…];
}
This is dumb, because it pollutes the source with boilerplate code and it’s easy to miss something. Is there a better way to set up KVO on retained properties? I wish I could write something like Moose’s after hook to change the KVO after the property was changed.
In fact I realized I could watch the property itself:
[self addObserver:self forKeyPath:#"foo"…];
And then change the KVO when the property changes :-), but I do realize this is much more complicated than the hand-written setter I’d like to avoid.
Ideas?
How about using a key path? Say you want to observe changes on both the value1 and value2 properties of foo. You could use:
[self addObserver:self forKeyPath:#"foo.value1"];
[self addObserver:self forKeyPath:#"foo.value2"];
Then when those properties change, you'll get notifications.
Related
I am new to OSX programming.
I would like to do a very simple app with the purpose of learning. The app is a NSTextView and a label.
The idea is for the label to show the character count of the textview.
Looking at NSTextView delegate methods, I don't manage to see which method is called every time a character is typed. Something like textField's textShouldBeginEditing.
How do I do that?
-(void)awakeFromNib{
[[NSNotificationCenter defaultCenter]addObserver :self
selector:#selector(myTextChanged:)
name:NSTextDidChangeNotification
object:nil];
}
-(void)myTextChanged:(NSNotification *)aNotification{
[textfieldLabel setString:[NSString stringWithFormat:#"ld",[textField.string length]];
}
the background: I'm adding a print panel accessory view to a print dialog (using addAccessoryController:), with controls binded to NSPrintInfo printSettings values so the values are saved in the print presets. I'm having troubles observing printSettings changes. I'm building with SDK 10.6, testing on OS X 10.7.
Here is a code sample that should work in my understanding, but observeValueForKeyPath: is never called:
- (void)testKVO
{
NSPrintInfo *printInfo = [NSPrintInfo sharedPrintInfo];
[printInfo addObserver:self forKeyPath:#"printSettings.foo" options:0 context:NULL];
[printInfo setValue:#"bar" forKeyPath:#"printSettings.foo"]; // observeValueForKeyPath:ofObject:change:context: not called
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(#"%s %# :: %#", _cmd, keyPath, object);
}
I also tried observing printSettings directly, with no more success, the observer method is not called either (the printSettings returned by NSPrintInfo is in fact of class NSPrintInfoDictionaryProxy):
- (void)testKVO
{
NSMutableDictionary *printSettings = [[NSPrintInfo sharedPrintInfo] printSettings];
[printSettings addObserver:self forKeyPath:#"foo" options:0 context:NULL];
[printSettings setValue:#"bar" forKey:#"foo"]; // observeValueForKeyPath:ofObject:change:context: not called
}
I double checked that my KVO system works in normal conditions and calls the observer method:
- (void)testKVO
{
NSMutableDictionary *printSettings = [NSMutableDictionary dictionary];
[printSettings addObserver:self forKeyPath:#"foo" options:0 context:NULL];
[printSettings setValue:#"bar" forKey:#"foo"]; // observeValueForKeyPath:ofObject:change:context: called at last!
}
So the question is: how can I observe printSettings modifications, especially to know when the user has chosen a print preset?
I'd also like the preview to be updated automatically with
- (NSSet *)keyPathsForValuesAffectingPreview
{
return [NSSet setWithObjects:
#"representedObject.printSettings.foo",
nil];
}
there is an easy workaround for the preview update: adding an indirection level by redeclaring the properties directly on the NSViewController itself. But for the print preset change I have no clue.
In the end, here is the comment in NSPrintInfo.h:
- (NSMutableDictionary *)printSettings;
The print info's print settings. You can put values in this dictionary to store them in any preset that the user creates while editing this print info with a print panel. Such values must be property list objects. This class is key-value coding (KVC) and key-value observing (KVO) compliant for "printSettings" so you can often bind controls in print panel accessory views directly to entries in this dictionary. You can also use this dictionary to get values that have been set by other parts of the printing system, like a printer driver's print dialog extension (the same sort of values that are returned by the Carbon Printing Manager's PMPrintSettingsGetValue() function). Other parts of the printing system often use key strings like "com.apple.print.PrintSettings.PMColorSyncProfileID" but dots like those in key strings wouldn't work well with KVC, so those dots are replaced with underscores in keys that appear in this dictionary, as in "com_apple_print_PrintSettings_PMColorSyncProfileID". You should use the same convention when adding entries to this dictionary.
Any help appreciated
Thanks
Well I found a way. There is an undocumented notification that is being sent when selecting a print preset or changing paper format, all that you have to do is add an observer:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(printInfoDidChange:) name:#"NSPrintInfoDidChange" object:nil];
That is not as straightforward as binding to printSettings keypaths, and I really don't like using an undocumented notification (almost as bad as using private API in term of maintainability) but that's the only way I could figure out to do the job.
From my understanding of Core Data, all that is necessary for primitive accessors to work is the #dynamic directive for the property name (as well as declaring primitive accessors for that property within the entity implementation).
For some reason, when using the generated primitive accessor the setState: method is not modifying the state property:
- (int)state
{
NSNumber * tmpValue;
[self willAccessValueForKey:#"state"];
tmpValue = [self primitiveState];
[self didAccessValueForKey:#"state"];
return [tmpValue intValue];
}
- (void)setState:(int)value
{
[self willChangeValueForKey:#"state"];
[self setPrimitiveState:[NSNumber numberWithInt:value]];
[self didChangeValueForKey:#"state"];
}
while using the key-value-coding version does modify the state property
- (int)state
{
NSNumber * tmpValue;
[self willAccessValueForKey:#"state"];
tmpValue = [self primitiveValueForKey:#"state"];
[self didAccessValueForKey:#"state"];
return [tmpValue intValue];
}
- (void)setState:(int)value
{
[self willChangeValueForKey:#"state"];
[self setPrimitiveValue:[NSNumber numberWithInt:value] forKey:#"state"];
[self didChangeValueForKey:#"state"];
}
in both cases, I primitive accessors are declared as follows (and as per Apple's example and code generation):
#interface Post (CoreDataGeneratedPrimitiveAccessors)
- (NSNumber *)primitiveState;
- (void)setPrimitiveState:(NSNumber *)value;
#end
I'm a bit at a loss to why this would be. Any help would be greatly appreciated!
After tremendous amounts of head-scratching, debugging, fiddling and guess-and-check, I've finally figured out what the problem is: Core Data primitive accessors AREN'T dynamically generated if you define those attributes as instance variables. I had defined them for debugging purposes (as GBD cannot see the values of properties without defined ivars, it seems), and this prevented primitive accessors from being generated correctly. This is something that Apple should really document in some form. As it's very difficult to discover on one's own. I hope this helps others who've been having the same issue!
I've been looking into this and one of the things discovered is that, contrary to docs, the implementation file generated from the data model does NOT list the primitive dynamic accessors. Other places state that you have to add them yourself. Could that be the issue?
Are you using/modifying the code of an NSManagedObject generated by Xcode? I believe that by default these are generated as "commented" out by an #if 0 directive.
Just wanted to say that I am having the same problem and had to switch to setPrimitiveValue and primitiveValueForKey based on your comment here. It bothers me that the default implementation does not work. Of note in my case is that I am subclassing another NSManagedObject. Not sure if that's your case as well.
I have a question regarding using KVO-compliant methods to insert/remove objects from an array. I'm working through Aaron Hillegass' Cocoa Programming for Mac OS X and I saw the following line of code (in the insertObject:inEmployeesAtIndex: method:
[[undoManager prepareWithInvocationTarget:self] removeObjectFromEmployeesAtIndex:index];
Correct me if I'm wrong, but I always thought it was better to call mutableArrayValueForKey: and then removeObjectAtIndex:...so I tried changing the above line to this:
[[undoManager prepareWithInvocationTarget:[self mutableArrayValueForKey:#"employees"]] removeObjectAtIndex:index];
And it didn't work. Can someone explain the difference and why the first line works but the second line doesn't?
UPDATE: My removeObjectFromEmployeesAtIndex:index method is implemented to make my collection class (an instance of NSMutableArray) KVC-compliant. So ultimately, calling [[self mutableArrayValueForKey:#"employees"] removeObjectAtIndex:index]; should end up calling [self removeObjectFromEmployeesAtIndex:index];
In your update you say:
calling [[self mutableArrayValueForKey:#"employees"] removeObjectAtIndex:index]; should end up calling [self removeObjectFromEmployeesAtIndex:index];
Unfortunately this is not correct not matter what is in your removeObjectFromEmployeesAtIndex: method as NSMutableArray will never call any methods in your class. Since you seem to be trying to get undo/redo functionality you have to use a method like removeObjectFromEmployeesAtIndex:. Otherwise when you hit undo for adding an employee you will have no way to 'redo' adding that employee. You also could have issues with undo/redo for edits to individual employees. If you wanted to you could change the line in the removeObjectFromEmployeesAtIndex: method that reads [employees removeObjectAtIndex:index]; to [[self valueForKey:#"employees"] removeObjectAtIndex:index]; or [self.employees removeObjectAtIndex:index]; but there is really no reason to go this route.
Yes. The first line (from the book) is basically equivalent to this:
id tmp = [undoManager prepareWithInvocationTarget:self];
[tmp removeObejctFromEmployeesAtIndex:index];
Your code, however, is basically equivalent to this:
id tmp1 = [self mutableArrayValueForKey:#"employees"];
id tmp2 = [undoManager prepareWithInvocationTarget:tmp1];
[tmp2 removeObjectAtIndex:index];
In other words, the target that you're preparing the invocation with is different in your code (unless self happens to be the same object as [self mutableArrayValueForKey:#"employees"], which is doubtful).
Is there any way to prevent the NSTokenField to select everything when pressing the ENTER key or when making to the first responder maybe using the TAB key?
An NSTokenField is a subclass of NSTextField. There's no easy, direct way to directly manipulate the selection of these classes (aside from -selectText:, which selects all).
To do this when it becomes the first responder, you'll need to subclass NSTokenField (remember to set the class of the field in your XIB to that of your custom subclass) and override -becomeFirstResponder like so:
- (BOOL)becomeFirstResponder
{
if ([super becomeFirstResponder])
{
// If super became first responder, we can get the
// field editor and manipulate its selection directly
NSText * fieldEditor = [[self window] fieldEditor:YES forObject:self];
[fieldEditor setSelectedRange:NSMakeRange([[fieldEditor string] length], 0)];
return YES;
}
return NO;
}
This code first looks to see if super answers "yes" (and becomes the first responder). If it does, we know it will have a field editor (an NSText instance), whose selection we can directly manipulate. So we get its field editor and set its selected range (I put the insertion point at the end with a { lastchar, nolength } range).
To do this when the field is done editing (return, tabbing out, etc.), override -textDidEndEditing: like this:
- (void)textDidEndEditing:(NSNotification *)aNotification
{
[super textDidEndEditing:aNotification];
NSText * fieldEditor = [[self window] fieldEditor:YES forObject:self];
[fieldEditor setSelectedRange:NSMakeRange([[fieldEditor string] length], 0)];
}
In this case, when the user ends editing, this method lets super do its thing, then it looks to see if it's still the first responder. If it is, it does the same as above: puts the insertion carat at the end of the field.
Note, this behavior is not standard and is unexpected. Use sparingly.