views:

1417

answers:

3

I'm reading a custom table cell in tableView:cellForRowAtIndexPath: from a nib file. This works great for my purposes, except it's quite slow.

Now, I know the right thing to do in the long term is to create the cell entirely in code, and to use a single view, and so on. But this is a prototype, and I don't want to put that much effort into it.

For now, I'd be happy if I was reading the nib only once in the UIViewController subclass, then tableView:cellForRowAtIndexPath: made copies of it. My assumption here is that copying would be faster than reading the nib.

Here's what I use to load the nib, which I call from viewDidLoad: (and retain after)

-(id)loadFromNamed:(NSString*)name {
 NSArray *objectsInNib = [[NSBundle mainBundle] loadNibNamed:name
                owner:self
              options:nil];
 assert( objectsInNib.count == 1 );
 return [objectsInNib objectAtIndex:0];
}

All is good so far. But the question is: How do I copy this over and over? Is it even possible?

I tried [_cachedObject copy] and [_cachedObject mutableCopy] but UITableViewCell doesn't support either copy protocol.

If I have to, I can just tell them to ignore the speed until I'm prepared to remove the nib entirely, but I'd rather get it going a little faster if there's a low-hanging fruit here.

Any ideas?

+5  A: 

Use the cell cloning built into the table view. Apple knew generating a lot of table cells was slow. Check out the docs for this method:

- (UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier

You create the cell once, then as new cells are requested, use that method to clone the existing cells. Then you just change what needs to be changed about the new cell and return the cell object.

Also check out the table view realted sample code provided by Apple that uses this method and show you the right way. The fact your cell was loaded from a nib shouldn't matter at all.


Minor clarification: I dont think the above method clone cells for you. Instead it takes cell object that have scrolled off the screen and simply moves them to a new spot. So it's literally reusing a cell. So be sure your custom table view can be set to all the new values it needs outside of the intialization.

Squeegy
Thank you. I had forgotten about `dequeueReusableCellWithIdentifier`. Now I'm using it, but it's still slower than I'd like. That suggests that the bottleneck is in drawing, which will have to wait until I have time to apply the other fast table tricks.But now I know what's going on!
Steven Fisher
Well your approach would have been even slower since reusing objects is far quicker than cloning objects. Glad I could help.
Squeegy
Try profiling on the device to see for certain what is running slow. Some people say the trick is to use only one view per cell and do all your own drawing.
Chris Lundie
This is not actually the correct answer to this question. On initial setup dequeuing will return nil for each visible cell. So in that `if (cell == nil)` block you will want to create a new UITableViewCell, probably by loading from the nib. I would also prefer to copy the UITableViewCell from an IBOutlet in my controller which I've wired up to the designed UITableViewCell in the nib. But copy fails, saying it doesn't support copyWithZone.
dlamblin
dlamblin, you're in exactly the situation that inspired my question. But I accepted this as the correct answer because I realized that if I'm using the cache, the most times I'll be running through loadNibNamed is once per initially visible row. Say, 5 times. I just don't need to optimize that; the real bottleneck has to be in the drawing, or in my repurposing the cell for different rows later in the table. (Haven't solved this yet, as I've been on another project.)
Steven Fisher
Is there a way to create a prototype instance of `UITableViewCell` in Interface Builder and just clone that as many as needed by the `UITableView` ? It's much easier than setting up the cell by code.Thanks.
adib
@adib There is a class is SDK 4.0 that helps with this called `UINib`. But not an easy to do it in the currently available OS.
Squeegy
+2  A: 

Well, I'm not sure why all the tutorials out there doesn't specify this step.

When using your own custom UITableViewCell from Nib, calling dequeueReusableCellWithIdentifier is not enough. You have to specify the "Identifier" in the IB, just for for it in the Table View Cell tab section.

Then make sure the identifier you put in IB is the same as the identifier you use for the dequeueReusableCellWithIdentifier.

Seymour Cakes
+1  A: 

Not proud of this solution, but it works with the maximum number of possible IB bindings:

Interface (AlbumTableViewCell is a subclass of UITableViewCell of which an instance is defined in AlbumViewController's XIB file):

@interface AlbumsViewController : UITableViewController {
    IBOutlet AlbumTableViewCell *tableViewCellTrack;
}

@property (nonatomic, retain) AlbumTableViewCell *tableViewCellTrack;

Implementation (unarchive / archive makes a copy / clones the table view cell):

@implementation AlbumsViewController

@synthesize tableViewCellTrack;

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    AlbumTableViewCell *cell = (AlbumTableViewCell *)[tableView dequeueReusableCellWithIdentifier: @"AlbumCell"];

    if (cell == nil) {
        AlbumsViewController *albumsViewController = [[[AlbumsViewController alloc] init] autorelease];
        [[NSBundle mainBundle] loadNibNamed: @"AlbumsViewController" owner: albumsViewController options: nil];

        cell = albumsViewController.tableViewCellTrack;
    }

    cell.labelTitle.text = ...;
    cell.labelArtist.text = ...;

    return cell;
}
pfo