Question

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 programatically, because MyView is a custom view and I don't want to create an IB palette for it.)

Was it helpful?

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.

OTHER TIPS

I can see two possibilities here:

First, do you instantiate the NSMutableArray object (and release it) in your MyDocument class? It should look something like this:

- (id)init
{
    if ((self = [super init]) == nil) { return nil; }
    myArray = [[NSMutableArray alloc] initWithCapacity:0];
    return self;
}

- (void)dealloc
{
    [myArray release];
    [super dealloc];
}

Second, did you declare myViewArray as a property in MyView? It should look something like this:

// MyView.h:
@interface MyView : NSView
{
    NSMutableArray * myViewArray;
}
@property (assign) NSMutableArray * myViewArray;
@end
    // MyView.m:
    @implementation MyView
    
    @synthesize myViewArray;
    
    @end
    

    Other than that, it looks to me like you have done all of the binding properly.

    update: How about using the NSArrayController to add items to the array:

    // MyView.h:
    @interface MyView : NSView
    {
        NSMutableArray * myViewArray;
        IBOutlet NSArrayController * arrayController;
    }
    @property (assign) NSMutableArray * myViewArray;
    - (void)someMethod;
    @end
    
      // MyView.m:
      @implementation MyView
      
      @synthesize myViewArray;
      
      - (void)someMethod
      {
          id someObject = [[SomeClass alloc] init];
          [arrayController addObject:[someObject autorelease]];
      }
      
      @end
      

      The problem appears to be that I had been binding MyView's myViewArray to the NSArrayController's arrangedObjects property instead of its content property.

      When binding to arrangedObjects, I found that the actual object pointed to by myViewArray was an instance of NSControllerArrayProxy. I didn't find a definitive answer as to what this object actually does when I searched online for more information on it. However, the code examples I found suggest that NSControllerArrayProxy is intended to expose conveniences for accessing the properties of objects in the array, rather than the objects (in the array) themselves. This is why I believe that I was mistaken in binding to arrangedObjects.

      The solution was to instead bind MyView's myViewArray to the NSArrayController's content property:

      - (void)windowControllerDidLoadNib:(NSWindowController *) aController
      {
          [super windowControllerDidLoadNib:aController];
      
          [myArrayController setContent:myArray];
          [myView bind:@"myViewArray" toObject:myArrayController withKeyPath:@"content" options:nil];
      }
      

      Although this appears to work, I am not 100% sure that it is correct to bind to content in this case. If anyone can shed some light on programmatically binding to the various properties of an NSArrayController, I would welcome comments to this answer. Thanks.

      First of all, there's nothing wrong with binding to arrangedObjects: an NSTableColumn, for instance, should have its content bound to arrangedObjects only, and its contentValues to arrangedObjects.someProperty.

      The common mistake is to regard arrangedObjects as the content of an arrayController but that, as you have seen, will lead to grief: arrangedObjects is a representation of the way the arrayController has currently arranged the objects in its content, not the content itself.

      That said, the way to bind an array to an arrayController is:

      [self.myArrayController bind:NSContentArrayBinding 
       toObject:self
       withKeyPath:@"myView.myViewArray" 
       options:nil]; 
      

      Are you sure, by the way, your view needs to hold the myViewArray? That usually falls under the responsibility of a controller or model object.

      Now you can add objects by calling addObject on the arrayController, since that is the controller's responsibility.

      [self.myArrayController addObject: anObject]
      
      Licensed under: CC-BY-SA with attribution
      Not affiliated with StackOverflow
      scroll top