doovers
doovers

Reputation: 8675

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

Upvotes: 1

Views: 163

Answers (3)

NSGod
NSGod

Reputation: 22948

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.

Upvotes: 2

Volodymyr Sapsai
Volodymyr Sapsai

Reputation: 413

How exactly do you fill the bound array at startup? Do you use NSArray or NSMutableArray? I don't know what you problem is, but the possible reasons for bindings not being updated:

  • setting instance variable directly, not via property;
  • modifying NSMutableArray state. For example, [self.mutableArrayProperty addObject:@"Value"] won't update bindings bound to mutableArrayProperty.

Bindings rely on Key-Value Observing and they are updated when some value is set to bound property, not when returned by bound property object changes its state.

Upvotes: 1

Luca Iaco
Luca Iaco

Reputation: 3457

Basically the common step to follow for the table view are:

  • Put the tableview in your view controller ( through xib or programmatically ) and reference outlet to your view controller (eg: call it myTableView )

  • bind the delegate and datasource to your view controller ( through xib or programmatically)
    (delegates in your view controller are UITableViewDataSource, UITableViewDelegate)

  • keep an array (maybe as properties of your view controller, let's suppose to call it NSMutableArray * mySource) and fill up with some objects (eg: this could be done in your viewDidLoad if not yet populated)

  • implement the delegated methods of tableview like:

    • - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView that define the number of sections in your tableview (for now you could simply return 1 )

    • - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section that should return your datasource array count (return [self.mySource count])

    • - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath that dequeue/create the cells to present into your myTableView

  • Once the UI views have been loaded call the method of your table view [self.myTableView reloadData] (NB: i suggest you to call it at least in the -(void)viewDidAppear:(BOOL)animated to be sure it will be really performed, or after pressing button in your view).

Then, each time you will add an item and you want to show the updated datsource in the tableview, simply call [self.myTableView reloadData] again.

Hope it helps!

Upvotes: 1

Related Questions