Search code examples
objective-ccocoacocoa-bindingskey-value-observingkey-value-coding

How to add an object to a programmatically bound NSMutableArray?


I have an NSDocument which has the following structure:

@interface MyDocument : NSDocument
{
    NSMutableArray *myArray;

    IBOutlet NSArrayController *myArrayController;
    IBOutlet MyView *myView;
}
@end

I instantiate the NSArrayController and the MyView in MyDocument.xib, and have made the connections to the File's Owner (MyDocument), so I am pretty sure that from the point of view of Interface Builder, I have done everything correctly.

The interface for MyView is simple:

@interface MyView : NSView {
    NSMutableArray *myViewArray;
}
@end

Now, in MyDocument windowControllerDidLoadNib, I have the following code:

- (void)windowControllerDidLoadNib:(NSWindowController *) aController
{
    [super windowControllerDidLoadNib:aController];
    [myArrayController setContent:myArray];
// (This is another way to do it)    [myArrayController bind:@"contentArray" toObject:self withKeyPath:@"myArray" options:nil];

    [myView bind:@"myViewArray" toObject:myArrayController withKeyPath:@"arrangedObjects" options:nil];
}

In the debugger, I have verified that myViewArray is an NSControllerArrayProxy, so it would appear that my programmatic binding is correct. However, when I try to add objects in MyView's methods to the MyView myViewArray, they do not appear to update the MyDocument's myArray. I have tried both of the following approaches:

[myViewArray addObject:value];
[self addMyViewArraysObject:value];

(The second approach causes a compiler error, as expected, but I thought that the Objective-C runtime would "implement" this method per my limited understanding of KVO.)

Is there something wrong with how I'm trying to update myViewArray? Is there something wrong with my programmatic binding? (I am trying to do this programmatically, because MyView is a custom view and I don't want to create an IB palette for it.)


Solution

  • The problem is that you're mutating your array directly. Implement indexed accessor methods and call those.

    KVO overrides your accessor methods (as long as you conform to certain formats) and posts the necessary notifications. You don't get this when you talk directly to your array; anything bound to the property won't know that you've changed the property unless you explicitly tell it. When you use your accessor methods, KVO tells the other objects for you.

    The only time to not use your accessor methods (synthesized or otherwise) is in init and dealloc, since you would be talking to a half-inited or -deallocked object.

    Once you're using your own accessor methods to mutate the array, and thereby getting the free KVO notifications, things should just work:

    • The view, when mutating its property, will automatically notify the array controller, which mutates its content property, which notifies your controller.
    • Your controller, when mutating its property, will automatically notify the array controller, which mutates its arrangedObjects property, which notifies the view.