Search code examples
objective-cnstableviewcocoa-bindings

Bound tableview not being filled at startup


I started learning about cocoa binding today and have successfully got it to work with one exception. That is, when I fill the bound array at startup, the tableview does not reflect this data until I add a new entry from the UI, at which point it will show the rest of the data that was in the array.

Do I need to add some code to 'refresh' the tableview after filling the array it is bound to?

Edit: here is my load method called from awakeFromNib:

- (void)load
{
    NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
    NSData *savedArray = [currentDefaults objectForKey:@"components"];
    if (savedArray != nil)
    {
        NSArray *oldArray = [NSKeyedUnarchiver unarchiveObjectWithData:savedArray];
        if (oldArray != nil) {
            _components = [[NSMutableArray alloc] initWithArray:oldArray];
        } else {
            _components = [[NSMutableArray alloc] init];
        }
    }
}

Where _components is the bound source for the NSTableView


Solution

  • One way to have the table populated at launch would be to call load from within your init method rather than waiting until awakeFromNib. For example:

    - (id)init {
         if ((self = [super init])) {
             NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
             NSData *savedArray = [currentDefaults objectForKey:@"components"];
             if (savedArray != nil)
             {
                 NSArray *oldArray = [NSKeyedUnarchiver unarchiveObjectWithData:savedArray];
                 if (oldArray != nil) {
                    _components = [[NSMutableArray alloc] initWithArray:oldArray];
                 } else {
                    _components = [[NSMutableArray alloc] init];
                 }
             }
         }
         return self;
    }
    

    (Note you should change this to the designated init method for the class you're using).

    What is happening now is that before awakeFromNib is called, the NSArrayController is being created, and its initial content is being set to a nil array (since _components hasn't been created yet). Then awakeFromNib is called, and you are creating _components array, but in a way that is not key-value-observer-compliant (you are setting the instance variable directly, which doesn't allow the NSArrayController to know that you've modified the array it's interested in).

    By creating the _components instance variable earlier on in init, you assure that it will be available by the time the NSArrayController is unarchived from the nib file. It can then set its initial contents to that array so there is data in the table view when the window appears.

    Regarding your comment about how to modify _components in a way that is KVC/KVO compliant, there are a couple of different ways, some of which are more efficient than others. You could call mutableArrayValueForKey: like so:

     [[self mutableArrayValueForKey:@"components"] addObject:theObject]; 
    

    See Troubleshooting Cocoa Bindings: My collection controller isn't displaying the current data.

    A more efficient way, though, is to add and remove items from the NSArrayController itself rather than adding and removing from the underlying array. This lets the array controller know that changes are being made as well as letting the array controller handle modifying the underlying array's contents. See Programmatically Modifying a Controller’s Contents.