Why are my Cocoa bindings broken? - objective-c

I have a window with an NSTextField (in Snow Leopard), which I have binded to an NSString function in my WindowController class. This string will combine information about my table view's selection and count, provided by my array controller. It gets an initial value, "0 0", but doesn't ever update, when the selection or count changes. The binding looks like this (File's Owner is MyWindowController):
I implemented + (NSSet *)keyPathsForValuesAffecting<key> (below), but the binding never updates, even when the array controller's total count and selection change.
(Additional troubleshooting performed) I had originally been using the Display Pattern Value binding of the NSTextField, but I needed more complicated logic than that binding afforded. I then started listening to the selection changed/changing events of the TableView that displays the array controller's contents and changing the Display Pattern Value bindings dynamically, but that felt like a hack, and overly complicated.
I'm sure there's something I'm missing, but I can't tell what. Does anyone have any ideas? I've read through Apple's key-value-observing documentation, and this seems to be all that's necessary. I've checked, and my keyPathsForValuesAffectingMyString is getting called, but myString only gets called once. I've distilled my code below (updated x3).
Update 1/21
I'm still plugging away trying to figure this out. When I addObserver to self for the arrayController key paths, the notifications do fire as expected, so my key paths and the key value observing mechanism is fine. When I call [self didChangeValueForKey:#"myString"]; within my observeValueForKeyPath method for the same keys, the binding still doesn't update, leading me to believe it's a bindings problem rather than a KVO problem. I'm going to be reading up on the bindings mechanism more...
#interface MyWindowController : NSWindowController {
IBOutlet NSArrayController *arrayController;
}
- (NSArrayController *)arrayController;
- (NSString *)myString;
#end
#implementation MyWindowController
+ (NSSet *)keyPathsForValuesAffectingMyString {
return [NSSet setWithObjects:
#"arrayController.arrangedObjects",
#"arrayController.selection",
nil];
}
- (NSArrayController *)arrayController {
return arrayController;
}
- (NSString *)myString {
// Just as an example; I have more complicated logic going on in my real code
return [NSString stringWithFormat:#"%#, %#",
[arrayController valueForKeyPath:#"arrangedObjects.#count"],
[arrayController valueForKeyPath:#"selection.#count"]];
}
#end

I’ve verified this exact same bug. Someone on Cocoabuilder had a guess as to why the bug happens:
http://www.cocoabuilder.com/archive/cocoa/284396-why-doesn-nsarraycontroller-selection-et-al-fire-keypathsforvaluesaffectingkey.html#284400
I can’t speak as to whether this explanation is true, but I certainly can’t get +keyPathsForValues… to work with NSArrayControllers.

I've got a workaround, but I'm not happy about it, since it shouldn't be necessary, and I would still prefer to get the bindings working properly. I won't accept this answer, and will delete it if someone posts an actual fix. </disclaimer>
#interface MyWindowController : NSWindowController {
IBOutlet NSArrayController *arrayController;
IBOutlet NSTextField *fieldThatShouldBeBinded;
}
- (NSString *)myString;
#end
#implementation MyWindowController
- (void)awakeFromNib {
[arrayController addObserver:self
forKeyPath:#"selection"
options:0
context:NULL];
[arrayController addObserver:self
forKeyPath:#"arrangedObjects"
options:0
context:NULL];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if( object == arrayController )
[fieldThatShouldBeBinded setStringValue:[self myString]];
}
- (NSString *)myString {
return [NSString stringWithFormat:#"%#, %#",
[arrayController valueForKeyPath:#"arrangedObjects.#count"],
[arrayController valueForKeyPath:#"selection.#count"]];
}
#end

Make sure that the arrayController outlet is connected in Interface Builder. I'm guessing that it's nil.

Don't use the #count keyword. Bindings and KVO on array controllers will get updated when the content changes. If that doesn't work, then there is a problem somewhere else.
Another option is to use the display pattern bindings instead of a composite property. Bind Display Pattern Value1 to arrayController.arrangedObjects.#count and Display Pattern Value2 to arrayController.selection.#count, and set the pattern to "%{value1}#, %{value2}#"

I met the same problem and found another way (but it is still workaround).
You have to declare dynamic workaround property. In implementation section, just return new empty object for it. Now, you can KVO this workaround property.
#property(nonatomic,retain) NSArray *workaround;
#dynamic workaround;
- (NSArray *)workaround { return [NSArray array]; } // new *every* time
- (void)setWorkaround:(NSArray *)unused { }
+ (NSSet *)keyPathsForValuesAffectingMyString { return [NSSet setWithObject:#"workaround"]; }
To get this work, you still need to manually bind self.workaround to arrayController.selectedObjects (or whatever):
- (void)awakeFromNib // or similar place
{
[super awakeFromNib];
[self bind:#"workaround" toObject:arrayController withKeyPath:#"selectedObjects" options:nil];
}
Manual binding works as expected, workaround is updated with what you have bound it to. But KVO tests whether property value is really changed (and stops propagating if it is the same). If you return new self.workaround value every time, it works.
Warning: never call -[setWorkaround:] by yourself — this will effectively flush the other side of binding (arrayController.selectedObjects in this case).
This method has some benefits: you avoid centralized observeValueForKeyPath:... and your logic is in the right place. And it scales well, just add workaround2, 3, and so on for similar cases.

Related

Subclassing NSPopUpButton to add a bindable property

I'm trying to add a bindable property to a custom NSPopUpButton subclass.
I've created a "selectedKey" property, which is meant to store a NSString associated with selected menu item.
In control init, I set self as button target and an action for the button (valueChanged:), which in turn sets "selectedKey" in accordance with user selection:
#interface MyPopUpButton : NSPopUpButton {
NSMutableDictionary *_items;
NSString *_selectedKey;
}
#property(nonatomic, readwrite, copy) NSString* selectedKey;
- (void)addItemWithTitle:(NSString *)title andKey:(NSString *)key;
#end
#implementation MyPopUpButton
- (instancetype)initWithFrame:(NSRect)frameRect {
self = [super initWithFrame:frameRect];
if (self) {
_items = [NSMutableDictionary new];
[NSObject exposeBinding:#"selectedKey"];
[super setTarget:self];
[super setAction:#selector(valueChanged:)];
}
return self;
}
- (void)addItemWithTitle:(NSString *)title andKey:(NSString *)key {
[super addItemWithTitle:title];
[_items setValue:title forKey:key];
}
- (void)valueChanged:(id)sender {
for (NSString *aKey in [_items allKeys]) {
if ([[_items valueForKey:aKey] isEqualToString:[self titleOfSelectedItem]]) {
self.selectedKey = aKey;
}
}
}
- (void)setSelectedKey:(NSString *)selectedKey {
[self willChangeValueForKey:#"selectedKey"];
_selectedKey = selectedKey;
[self didChangeValueForKey:#"selectedKey"];
[self selectItemWithTitle:[_items valueForKey:selectedKey]];
}
#end
This seems to work as expected: "selectedKey" property is changed when user changes PopUpButton selection.
Unfortunately, trying to bind this property, doesn't work.
[selectButton bind:#"selectedKey" toObject:savingDictionary withKeyPath:key options:#{NSContinuouslyUpdatesValueBindingOption : #YES }]
When selection is changed bind object is not updated accordingly.
What am I doing wrong?
I've created a "selectedKey" property, which is meant to store an NSString associated with selected menu item.
Bindings is definitely the way to go here, but your use of bind:toObject:withKeyPath:options is incorrect.
The value that you pass to the first argument must be one of the predefined values made available by Apple for that particular control. For NSPopUpButton objects, the available values are documented in the NSPopUpButton Bindings Reference. When you look through this document you'll see that there is no selectedKey option. There is however a selectedValue which has the following description:
An NSString that specifies the title of the selected item in the NSPopUpButton.
Thus the correct way to set up the binding is as follows:
[self.btn bind:#"selectedValue"
toObject:self
withKeyPath:#"mySelectedString"
options:nil];
This is all you need to do: when the action selector is fired the property stored at the keyPath you passed in as the third argument will already have been updated. This means that you can (i) get rid of the setSelectedKey method entirely, (ii) remove exposeBinding line, and (iii) remove the code within valueChanged: - Cocoa has already done this bit.
The example below implements just two methods, but, if I've understood your intentions, they should be all you need:
- (void)awakeFromNib {
self.btn.target = self;
self.btn.action = #selector(popUpActivity:);
[self.btn bind:#"selectedValue"
toObject:self
withKeyPath:#"mySelectedString"
options:nil];
// I've added a couple of additional bindings here; they're
// not required, but I thought they'd be instructive.
[self.btn bind:#"content"
toObject:self
withKeyPath:#"myItems"
options:nil];
[self.btn bind:#"selectedIndex"
toObject:self
withKeyPath:#"mySelectedIndex"
options:nil];
// Now that you've set the bindings up, use them!
self.myItems = #[#"Snow", #"Falling", #"On", #"Cedars"];
self.mySelectedIndex = #3; // "Cedars" will be selected on startup
// no need to set value of mySelectedString, because it will be
// updated automatically by the selectedIndex binding.
NSLog("%#", self.mySelectedString) // -> "Cedars"
}
- (void)popUpActivity:(id)sender {
NSLog(#"value of <selectedIndex> -> %#", self.mySelectedIndex);
NSLog(#"value of <selectedString> -> %#", self.mySelectedString);
}
A final point worth making is that none of the above should be a part of an NSPopUpButton subclass. It looks like you can - and therefore should - do everything you need to do without a custom subclass of this control. In my demo-app the code above belongs to the ViewController class, you should try doing this also.

awakeFromInsert called twice with nested contexts

This project uses Mogenerator and Magical Record. I have tracked down a bug to the fact that awakeFromInsert is getting called twice. Once for each of my contexts I presume. This is an issue because I need to listen for NSNotifications on this NSManagedObject like this:
- (void)awakeFromInsert
{
// Listen for a return from background mode
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(enteringForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
}
But awakeFromInsert get's called twice which is rather annoying. I want to call a method ONCE when my NSManagedObject is first created.
After searching this solution seems to make a lot of sense. However I can't see how I can add a category onto NSManagedObject when using Mogenerator and MagicalRecord. Without some complex overriding.
In MagicalRecord MR_createEntity calls
if ([self respondsToSelector:#selector(insertInManagedObjectContext:)])
{
id entity = [self performSelector:#selector(insertInManagedObjectContext:) withObject:context];
return entity;
}
Is there a neater solution to this issue?
Ok well this feels very hacky but appears to work. I created the following class methods on my human readable NSManagedObject class:
+ (id)insertInManagedObjectContext:(NSManagedObjectContext*)moc_ {
JWBoard *newobject = [super insertInManagedObjectContext:moc_];
[JWBoard awakeFromCreate:newobject];
return newobject;
}
+ (void)awakeFromCreate:(JWBoard *)board
{
// do setup stuff & add observers
}
Open to much better solutions!
Open to much better solutions!
I wish! Would have been easy enough for apple to not invoke awakeFromInsert, or to at least provide a flag that's true in the context of a "parentProcessSaveRequest." If you look at the call stack for the not-first calls to awakeFromInsert, the stack always contains parentProcessSaveRequest.
Here's some terrible code proving as much:
- (void) awakeFromInsert
{
[super awakeFromInsert];
NSArray* stackArray = [NSThread callStackSymbols];
for (NSString* method in stackArray)
{
if ([method rangeOfString:#"_parentProcessSaveRequest"].location != NSNotFound)
{
NSLog(#"Parent insert %#",self.objectID);
return;
}
}
NSLog(#"First insert %#",self.objectID);
// Initialize here
}
And the log output -- the objectId stays the same:
2014-05-19 20:53:52.964 myApp[1891:a01f] First insert 0x6000000326c0 <x-coredata:///MyEntity/t496E9B17-E170-4A7C-B7D4-7D8B92433E1C2>
2014-05-19 20:53:53.531 myApp[1891:303] Parent insert 0xdca8000eb <x-coredata://7274869F-4BF3-4B8A-9270-A64E54476AAD/MyEntity/p14122>
2014-05-19 20:53:53.537 myApp[1891:303] Parent insert 0xdca8000eb <x-coredata://7274869F-4BF3-4B8A-9270-A64E54476AAD/MyEntity/p14122>
Seems to work for saving to any of the nested contexts that I have, ugly as it is.
Unfortunately I cant figure any reasonable way to determine whether awakeFromInsert is being called in the context of a parentProcessSaveRequest. Come on, Apple! Give us a flag here.
here is the simplest one:
when parentContext is null, means when this context is saved you can do you custom logic, for example incrementing table number
- (void)awakeFromInsert
{
if (!self.managedObjectContext.parentContext) {
//setting tableNumber
[self willChangeValueForKey:#"number"];
[self setPrimitiveNumber:tableNumber];
[self didChangeValueForKey:#"number"];
}
}

NSMutableDictionary KVO

I'm trying to observe changes in dictionary using KVO.
Example:
dictionary = [NSMutableDictionary new];
[dictionary setObject:#"test1" forKey:#"key1"];
[dictionary setObject:#"test2" forKey:#"key2"];
[dictionary setObject:#"test3" forKey:#"key1"];
I'd love to be able to hook an observer for whenever a value is added to the dictionary. removed, or replaced (ie in the above cases, whenever any of the setObject methods are called)
So in conclusion:
I want a function to have
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
called when I ADD a any new entry to a dictionary, or Remove any entry, or REPLACE any entry.
NOT: I do NOT want to have to specify which keys I'm observing for. (eg observe only when #"key1" is added) as this solution doesn't scale.
Subclassing NSMutableDictionary is a bit annoying, due to the fact that NSDictionary and its friends are class clusters. It's certainly doable, and if you have to pass the object itself to another set of classes, then you may want to do exactly that. Otherwise, it might be easier to create a composite class which has the same basic API and uses NSMutableDictionary object internally for storage. There's a pretty good write-up as CocoaWithLove.com, Ordered Dictionary Subclassing, which goes into doing this.
However, that doesn't completely solve your problem. What I would suggest is that you begin with a subclass or decorator class such as the one above, then add support explicitly for -(NSArray*)allKeys, which is a standard accessor in NSDictionary itself. Then, you can add support to pass along change messages for allKeys, which will make it observable.
This can be done by adding the following code around the -setObject:forKey: and -removeObjectForKey: methods.
- (void)setObject:(id)anObject forKey:(id)aKey
{
BOOL addKey=NO;
if (![dictionary objectForKey: aKey]) {
addKey=YES;
[self willChangeValueForKey: #"allKeys"];
}
[dictionary setObject:anObject forKey:aKey];
if (addKey)
[self didChangeValueForKey: #"allKeys"];
}
- (void)removeObjectForKey:(id)aKey
{
[self willChangeValueForKey: #"allKeys"];
[dictionary removeObjectForKey:aKey];
[self didChangeValueForKey: #"allKeys"];
}
What is being done here is that we're adding explicit KVO notification to the class when the dictionary's keys are changed to mark a change in the array.
This will take care of adds and removes. If you want changes to be notified on the same basis, you can remove the if statements, and just have allKeys notify on either set or remove, like this:
- (void)setObject:(id)anObject forKey:(id)aKey
{
[self willChangeValueForKey: #"allKeys"];
[dictionary setObject:anObject forKey:aKey];
[self didChangeValueForKey: #"allKeys"];
}
Then, in your code, you put in a single observer for the key #"allKeys" on this object and you'll be receiving notifications whenever an item changes.
I solved a similar problem by adding an observer to the mutable dictionary "translator" in this way:
[self addObserver:self forKeyPath:#"translator.#count" options:0 context:NULL];
My app manages the data in a the classical way, using a tableview, a controller and the dictionary as KVO property "translator".
The dictionary is bound to a NSDictionaryController in my XIB, and a tableview content is bound to the controller.
This are the connections of the tableview:
Now, in any of the following cases I catch the change :
adding a key-value pair
removing a key-value pair
changing a key
changing a value
Remark: unfortunately, this approach does not work with NSMutableArrays.
Changes are not recognized
Can't you subclass NSMutableDictionary and override the various setters? For instance, overriding setObject:forKey: by calling super, then immediately calling addObserver...
You can also write a wrapper for NSMutableDictionary where you force yourself to use custom setters to manipulate the underlying NSMutableDictionary.
Maybe I need more context to any of your limitations or scalability intents.
I hope this will be helpful
- (void)addObserver:(id)observer {
for (id key in grid)
[self addObserver:observer
forKeyPath:[key description]
options:0
context:key];
}
I think another way to do this is using the below override, incase you are observing NSMutableDictionary "allRecentCurrencyData" whose values are dependent on recentBrazilReals, recentEuEuro, recentUkPounds, recentJapanYen, the observer will get called, but the drawback is you need to know the keys before hand to do this.
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
{
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:#"allRecentCurrencyData"]) {
NSArray *affectingKeys = #[#"recentBrazilReals", #"recentEuEuro",#"recentUkPounds",#"recentJapanYen"];
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
}
return keyPaths;
}

Xcode warning "Property access results unused - getters should not be used for side effects"

I'm getting this warning when I'm calling a local routine.
My code is this:
-(void)nextLetter {
// NSLog(#"%s", __FUNCTION__);
currentLetter ++;
if(currentLetter > (letters.count - 1))
{
currentLetter = 0;
}
self.fetchLetter;
}
I'm getting the warning on the self.fetchLetter statement.
That routine looks like this:
- (void)fetchLetter {
// NSLog(#"%s", __FUNCTION__);
NSString *wantedLetter = [[letters objectAtIndex: currentLetter] objectForKey: #"langLetter"];
NSString *wantedUpperCase = [[letters objectAtIndex: currentLetter] objectForKey: #"upperCase"];
.....
}
I prefer to fix warning messages, is there a better way to write this?
Thanks!
The dot notation (i.e. self.fetchLetter) is meant for properties, not for arbitrary methods. The self.fetchLetter is being interpreted as "get the 'fetchLetter' property of 'self'," which isn't what you intend.
Just use [self fetchLetter] instead.
In newer Xcode versions, even the [object method]; may trigger the warning. But sometimes we actually do need to call a property and discard the result, for example when dealing with view controllers and we need to make sure the view is actually loaded.
So we were doing:
// Ensure view is loaded and all outlets are connected.
[self view];
This now also triggers the “Property access results unused - getters should not be used for side effects” warning. The solution is to let the compiler know it's done intentionally by casting the result type to void:
(void)[self view];
You're declaring fetchLetter using syntax like this?
#property (retain) id fetchLetter;
That looks wrong for what you're doing. Properties are intended to be variable accessors that (in the case of getters) don't have any side effects.
You should declare fetchLetter as a method, like so:
- (void) fetchLetter;
and access it using:
[self fetchLetter]
I just got my problem resolved, in my case a CoreLocation Project, using both answers from Tom and Chris -
I declare:
#property (strong, nonatomic)CLLocationManager *locationManager;
And implemented like:
#synthesize locationManager = _locationManager;
....
- (void) dealloc {
[self locationManager];
}

How to get notified of changes to models via an NSArrayController?

I have an NSView subclass which is bound to the arrangedObjects of an NSArrayController. When the array has an item inserted or removed the view is notified. How do I get it to be notified if a model stored in the array has an attribute changed?
Do I need to add my view as an observer to every (relevant) attribute of every item added to the array?
When an item is added to or removed from the array I am notified via observeValueForKeyPath:ofObject:change:context: in my NSView subclass. I am not notified of changes to the models stored in the array but I could, every time I am notified of an insertion, add the view as an observer to the new item's attributes. Is this the best way to do this?
I overrode addObserver for the model class so that I could see what happens and noticed that NSTableView columns bound to the arrangedObjects add themselves as observers to the appropriate attributes. Can this be made to happen automagically or do I set up the observations manually?
A big thank you to dreamlax but I think I didn't do a good enough job explaining my problem. My model class was observable and produced the right notifications but I couldn't work out how to observe them without observing every item in the array directly.
I think the documentation for key paths could be improved because I couldn't find anything that explained the very simple change I needed to make. There's some good info the array magic keypaths but no simple "these are the common things" documentation.
Anyway. Previously in my NSView subclass I had the following:
- (void) bind:(NSString *)binding toObject:(id)observable withKeyPath:(NSString *)keyPath options:(NSDictionary *)options
{
if ([binding isEqualToString:#"observedObjects"]) {
[observable addObserver:self forKeyPath:#"arrangedObjects" options:0 context:nil];
} else {
[super bind:binding toObject:observable withKeyPath:keyPath options:options];
}
}
To get notification of changes to the models within the NSArrayController's arrangedObjects all I needed to add was observation of arrangedObjects.name (for the name property of my model). So the above code became:
- (void) bind:(NSString *)binding toObject:(id)observable withKeyPath:(NSString *)keyPath options:(NSDictionary *)options
{
if ([binding isEqualToString:#"observedObjects"]) {
[observable addObserver:self forKeyPath:#"arrangedObjects" options:0 context:nil];
[observable addObserver:self forKeyPath:#"arrangedObjects.name" options:0 context:nil];
} else {
[super bind:binding toObject:observable withKeyPath:keyPath options:options];
}
}
That's it! Now if any object in arrangedObjects gets its name changed I am notified.
Maybe rather than observing potentially many key value paths, why not have each object in the array post a notification when something has changed, then only one object needs to observe one notification instead of one object observing many key value paths.
EDIT:
Also, your arrayed objects could also respond to a class method called +keyPathsForValuesAffecting<key> where <key> is your key name. Here's an example: paymentDue is a key, which is affected when the values invoiceItems.count or paymentsMade have changed. When invoiceItems.count or paymentsMade has changed, anything bound to paymentDue is sent a notification.
+ (NSSet *) keyPathsForValuesAffectingPaymentDue:
{
return [NSSet setWithObjects:#"invoiceItems.count", #"paymentMade", nil];
}
If you are running on 10.4, or are targeting 10.4 or earlier, you'll need to use this method instead, but it essentially boils down to the same thing.
EDIT 2:
To clarify your other comment; you can still have each object in the array manually call
[[NSNotificationCenter defaultCenter] postNotificationName:#"ModelDidChange" object:self];
Then, with some controller code you can register for notification updates from your objects. If you choose a unique notification name then you won't need to manually listen from specific objects, you can tell the NSNotificationCenter that you want to receive notifications from any object. Your controller can work out which object has changed quite easily.
Register with the notification center (these methods should be in a controller object):
// This string could really be just about anything you want, but make it conspicuous.
static NSString * const ModelDidChangeName = #"ModelDidChange";
- (void) awakeFromNib
{
// using nil for the object parameter will make the notification center
// invoke modelDidChange: regardless of who the sender is.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(modelDidChange:) name:ModelDidChangeName object:nil];
}
Implement a method to handle the notification.
- (void) modelDidChange:(NSNotification *) notification
{
MyModelClass *myObject = [notification object];
// do stuff with your model if necessary.
// do stuff with your view too
}
Get your model objects to post notifications when parts of them change:
#implementation MyModelClass
- (void) setSomething:(NSString *) newThing
{
[something autorelease];
something = [newThing copy];
if (something == nil)
{
// special case scenario for when something is nil
// do stuff, modify MyModelClass instance's attributes
[[NSNotificationCenter defaultCenter] postNotificationName:ModelDidChange object:self];
// the controller's modelDidChange: method is automatically invoked.
}
}
#end
But
If your model is properly KVC compliant and made with the appropriate KVO callbacks, manual notifications won't be necessary.