views:

353

answers:

3

What do I need to do to update a tableView bound to an NSArrayController when a method is called that updates the underlying array? An example might clarify this.

When my application launches, it creates a SubwayTrain. When SubwayTrain is initialised, it creates a single SubwayCar. SubwayCar has a mutable array 'passengers'. When a Subway car is initialised, the passengers array is created, and a couple of People objects are put in (let's say a person with name "ticket collector" and another, named "homeless guy"). These guys are always on the SubwayCar so I create them at initialisation and add them to the passengers array.

During the life of the application people board the car. 'addPassenger' is called on the SubwayCar, with the person passed in as an argument.

I have an NSArrayController bound to subwayTrain.subwayCar.passengers, and at launch my ticket collector and homeless guy show up fine. But when I use [subwayCar addPassenger:], the tableView doesn't update. I have confirmed that the passenger is definitely added to the array, but nothing gets updated in the gui.

What am I likely to be doing wrong? My instinct is that it's KVO related - the array controller doesn't know to update when addPassenger is called (even though addPassenger calls [passengers addObject:]. What could I be getting wrong here - I can post code if it helps.

Thanks to anyone willing to help out.

UPDATE

So, it turns out I can get this to work by changing by addPassenger method from

[seatedPlayers addObject:person];

to

NSMutableSet *newSeatedPlayers = [NSMutableSet setWithSet:seatedPlayers];

[newSeatedPlayers addObject:sp];

[seatedPlayers release];

[self setSeatedPlayers:newSeatedPlayers];

I guess this is because I am using [self setSeatedPlayers]. Is this the right way to do it? It seems awfully cumbersome to copy the array, release the old one, and update the copy (as opposed to just adding to the existing array).

+2  A: 

I don't know if its considered a bug, but addObject: (and removeObject:atIndex:) don't generate KVO notifications, which is why the array controller/table view isn't getting updated. To be KVO-compliant, use mutableArrayValueForKey:

Example:

[[self mutableArrayValueForKey:@"seatedPlayers"] addObject:person];

You'll also want to implement insertObject:inSeatedPlayersAtIndex: since the default KVO methods are really slow (they create a whole new array, add the object to that array, and set the original array to the new array -- very inefficient)

- (void)insertObject:(id)object inSeatedPlayerAtIndex:(int)index
{
   [seatedPlayers insertObject:object atIndex:index];
}

Note that this method will also be called when the array controller adds objects, so its also a nice hook for thinks like registering an undo operation, etc.

+1  A: 

I haven't tried this, so I cannot say it works, but wouldn't you get KVO notifications by calling

insertObject:atArrangedObjectIndex:

on the ArrayController?

Lennart Bonnevier
+1  A: 

So, it turns out I can get this to work by changing by addPassenger method from

[seatedPlayers addObject:person];

to

NSMutableSet *newSeatedPlayers = [NSMutableSet setWithSet:seatedPlayers];
[newSeatedPlayers addObject:sp];
[seatedPlayers release];
[self setSeatedPlayers:newSeatedPlayers];

I guess this is because I am using [self setSeatedPlayers]. Is this the right way to do it?

First off, it's setSeatedPlayers:, with the colon. That's vitally important in Objective-C.

Using your own setters is the correct way to do it, but you're using the incorrect correct way. It works, but you're still writing more code than you need to.

What you should do is implement set accessors, such as addSeatedPlayersObject:. Then, send yourself that message. This makes adding people a short one-liner:

[self addSeatedPlayersObject:person];

And as long as you follow the KVC-compliant accessor formats, you will get KVO notifications for free, just as you do with setSeatedPlayers:.

The advantages of this over setSeatedPlayers: are:

  • Your code to mutate the set will be shorter.
  • Because it's shorter, it will be cleaner.
  • Using specific set-mutation accessors provides the possibility of specific set-mutation KVO notifications, instead of general the-whole-dang-set-changed notifications.

I also prefer this solution over mutableSetValueForKey:, both for brevity and because it's so easy to misspell the key in that string literal. (Uli Kusterer has a macro to cause a warning when that happens, which is useful when you really do need to talk to KVC or KVO itself.)

Peter Hosey