90

I am able to design custom UITableViewCells and load them just fine using the technique described in the thread found at http://forums.macrumors.com/showthread.php?t=545061. However, using that method no longer allows you to init the cell with a reuseIdentifier which means you have to create whole new instances of each cell at every call. Has anyone figured out a good way to still cache particular cell types for reuses, but still be able to design them in Interface Builder?

TheNeil
  • 3,321
  • 2
  • 27
  • 52
Greg Martin
  • 5,074
  • 3
  • 34
  • 35

16 Answers16

119

Actually, since you are building the cell in Interface Builder, just set the reuse identifier there:

IB_reuse_identifier

Or if you are running Xcode 4, check the Attributes inspector tab:

enter image description here

(Edit: After your XIB is generated by XCode, it contains an empty UIView, but we need a UITableViewCell; so you have to manually remove the UIView and insert a Table View Cell. Of course, IB will not show any UITableViewCell parameters for a UIView.)

Community
  • 1
  • 1
Tim Keating
  • 6,443
  • 4
  • 47
  • 53
  • What do I set the Identifiers, if I use this Nib created Cells for more than one cells? THis will lead us to have two cells with same identifiers. – RK- May 12 '11 at 13:41
  • 4
    Don't think of it as a unique identifier -- think of it more like a type name. – Tim Keating May 12 '11 at 14:37
  • I'm not seeing the option to set an identifier via the built-in interface builder in Xcode 4.3.3. I am definitely setting the class to my UITableViewCell subclass. Am I just missing it, or is it gone? – Tyler Jun 18 '12 at 23:28
  • 3
    Ok, I solved my problem. If you start with a UIView object in interface builder (within Xcode 4), and change its class to UITableViewCell, you don't get the cell-specific properties like the reuse identifier. To get that, you're supposed to start with an empty xib and drag in a table cell object, which will then have the cell-specific properties that you can edit. – Tyler Jun 19 '12 at 00:58
  • 1
    @Krishnan Think of it this way -- when you create the table view cell with identifier X, you are saying "Give me a cell from the pool labeled X." If the pool exists, and there is a free cell in there, then it gives it to you. Otherwise, it creates the pool (if necessary), then news up the cell, labels it "X", and then hands it to you. So cells CAN be unique -- e.g. you could create a pool with only one cell with a particular identifier -- but the library uses a free list-like strategy to avoid memory alloc/dealloc. – Tim Keating Jun 19 '12 at 03:42
  • @Tyler just checked this in XCode 5, and it's there, so I'm assuming yes on 4.3.3. – Tim Keating Nov 06 '13 at 21:12
74

Just implement a method with the appropriate method signature:

- (NSString *) reuseIdentifier {
  return @"myIdentifier";
}
Louis Gerbarg
  • 43,356
  • 8
  • 80
  • 90
  • Where to implement this method? – RK- May 12 '11 at 12:53
  • 2
    In your UITableViewCell subclass. http://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UITableViewCell_Class/Reference/Reference.html#//apple_ref/occ/instp/UITableViewCell/reuseIdentifier – DenTheMan Jun 20 '11 at 15:43
  • 5
    This is risky. What happens if you have two subclasses of your cell subclass, and use both of these in a single table view? If they send the reuse identifier call onto super you'll dequeue a cell of the wrong type.............. I think you need to override the reuseIdentifier method, but have it return a supplanted identifier string. – Ken Nov 04 '11 at 09:10
  • 3
    To be sure it's unique, you may do: `return NSStringFromClass([self class]);` – ivanzoid Jul 18 '13 at 12:27
66

Now, in iOS 5 there is an appropriate UITableView method for that:

- (void)registerNib:(UINib *)nib forCellReuseIdentifier:(NSString *)identifier
wzs
  • 1,057
  • 9
  • 12
48

I can't remember where I found this code originally, but it's been working great for me so far.

- (UITableViewCell *)tableView:(UITableView *)tableView 
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *CellIdentifier = @"CustomTableCell";
    static NSString *CellNib = @"CustomTableCellView";

    UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        NSArray *nib = [[NSBundle mainBundle] loadNibNamed:CellNib owner:self options:nil];
        cell = (UITableViewCell *)[nib objectAtIndex:0];
    }

    // perform additional custom work...

    return cell;
}

Example Interface Builder setup...

alt text

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
jrainbow
  • 621
  • 5
  • 4
12

Look at the answer I gave to this question:

Is it possible to design NSCell subclasses in Interface Builder?

It's not only possible to design a UITableViewCell in IB, it's desirable because otherwise all of the manual wiring and placement of multiple elements is very tedious. Performaance is fine as long as you are careful to make all elements opaque when possible. The reuseID is set in IB for the properties of the UITableViewCell, then you use the matching reuse ID in code when attempting to dequeue.

I also heard from some of the presenters at WWDC last year that you shouldn't make table view cells in IB, but it's a load of bunk.

Community
  • 1
  • 1
Kendall Helmstetter Gelner
  • 74,769
  • 26
  • 128
  • 150
  • 2
    You shouldn't make table view cells in IB if you need transparency and want good scrolling performance. For certain UIs, you need transparency (to render text over graphics e.g.). Sometimes the only way to achieve good scrolling on older (pre-A4) hardware is to render in code, to avoid the GPU having to composite multiple transparent layers. – Nick Forge Aug 24 '10 at 04:17
  • 2
    True, but even with that you might be better off letting performance suffer slightly on old devices for the easier maintainability of cells built in IB. You can also use this technique as a template cell that you then draw elements from to avoid compositing with a custom draw method. – Kendall Helmstetter Gelner Aug 24 '10 at 19:31
7

As of iOS circa 4.0, there are specific instructions in the iOS docs that make this work super-fast:

http://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/TableView_iPhone/TableViewCells/TableViewCells.html#//apple_ref/doc/uid/TP40007451-CH7

Scroll down to where it talks about subclassing UITableViewCell.

Ben Mosher
  • 13,251
  • 7
  • 69
  • 80
6

Here is another option:

NSString * cellId = @"reuseCell";  
//...
NSArray * nibObjects = [[NSBundle mainBundle] loadNibNamed:@"CustomTableCell" owner:nil options:nil];

for (id obj in nibObjects)
{
    if ([obj isKindOfClass:[CustomTableCell class]])
    {
        cell = obj;
        [cell setValue:cellId forKey:@"reuseIdentifier"];
        break;
    }
}
JDL
  • 1,477
  • 21
  • 31
  • Note that this is the only solution posted so far that doesn't require subclassing your custom `UITableViewCell` in order to set a unique value for `reuseIdentifer`. I think this is what the original op was actually looking for. – charshep Jan 18 '12 at 02:03
  • I hope Apple will not deny my application for using this... I am using this to get "static" cells because in my table view, the process of filling a cell is quiet slow. This way I just have to execute it once (giving a different identifier to each row). Thank you! – Ricard Pérez del Campo Jan 04 '13 at 16:34
  • Very helpful as there is no UITableViewCell method for setting this manually! – Jesse Dec 09 '13 at 22:16
2

I create my custom view cells in a similar manner - except I connect the cell through an IBOutlet.

The [nib objectAt...] approach is susceptible to changes to positions of items in the array.

The UIViewController approach is good - just tried it, and it works nice enough.

BUT...

In all cases the initWithStyle constructor is NOT called, so no default initialisation is done.

I have read various places about using initWithCoder or awakeFromNib, but no conclusive evidence that either of these is the right way.

Apart from explicitly calling some initialization method in the cellForRowAtIndexPath method I haven't found an answer to this yet.

sww
  • 21
  • 1
2
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *simpleTableIdentifier = @"CustomCell";

CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:nil];
    cell = [nib objectAtIndex:0];

    [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
}         

return cell;
}
Mohit
  • 3,708
  • 2
  • 27
  • 30
2

A while back I found a great blog post on this topic at blog.atebits.com, and have since started using Loren Brichter ABTableViewCell class to do all of my UITableViewCells.

You end up with a simple container UIView to put all of your widgets, and scrolling is lightning fast.

Hope this is useful.

eric
  • 391
  • 2
  • 13
2

This technique also works and doesn't require a funky ivar in your view controller for memory management. Here, the custom table view cell lives in a xib named "CustomCell.xib".

 static NSData *sLoadedCustomCell = nil;

 cell = [tableView dequeueReusableCellWithIdentifier:@"CustomCell"];
 if (cell == nil) 
 {
   if (sLoadedCustomCell == nil) 
   {        
      // Load the custom table cell xib
      // and extract a reference to the cell object returned
      // and cache it in a static to avoid reloading the nib again.

      for (id loadedObject in [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:nil options:nil]) 
      {
        if ([loadedObject isKindOfClass:[UITableViewCell class]]) 
        {
          sLoadedCustomCell = [[NSKeyedArchiver archivedDataWithRootObject: loadedObject] retain];
          break;
        }
    }
    cell = (UITableViewCell *)[NSKeyedUnarchiver unarchiveObjectWithData: sLoadedCustomCell];
  }
Bill Garrison
  • 2,039
  • 15
  • 18
  • 1
    Archiving and unarchiving is wholly unnecessary. – Bryan Henry Mar 17 '11 at 19:48
  • 1
    Archiving/unarchiving is unnecessary if you are fine with loading the cell from its nib whenever it cannot be dequeued. However, if you only want to load the cell from its nib exactly *once*, then you need to cache it in memory. I accomplish that caching using NSKeyedArchiving because UITableViewCell doesn't implement NSCopying. – Bill Garrison Mar 19 '11 at 22:10
  • 1
    All that said, using UINib to load the cell achieves the same effect: load from disk once, load from memory thereafter. – Bill Garrison Mar 19 '11 at 22:56
2

Louis method worked for me. This is the code I use to create the UITableViewCell from the nib:

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

    if (cell == nil) 
    {
        UIViewController *c = [[UIViewController alloc] initWithNibName:@"CustomCell" bundle:nil];
        cell = (PostCell *)c.view;
        [c release];
    }

    return cell;
}
ggarber
  • 8,300
  • 5
  • 27
  • 32
1

The gustavogb solution doesn't work for me, what I tried is :

ChainesController *c = [[ChainesController alloc] initWithNibName:@"ChainesController" bundle:nil];
[[NSBundle mainBundle] loadNibNamed:@"ChaineArticleCell" owner:c options:nil];
cell = [c.blogTableViewCell retain];
[c release];

It seems to work. The blogTableViewCell is the IBOutlet for the cell and ChainesController is the file's owner.

groumpf
  • 19
  • 1
1

From the UITableView docs regarding dequeueWithReuseIdentifier: "A string identifying the cell object to be reused. By default, a reusable cell’s identifier is its class name, but you can change it to any arbitrary value."

Overriding -reuseIdentifer yourself is risky. What happens if you have two subclasses of your cell subclass, and use both of these in a single table view? If they send the reuse identifier call onto super you'll dequeue a cell of the wrong type.............. I think you need to override the reuseIdentifier method, but have it return a supplanted identifier string. Or, if one has not been specified, have it return the class as a string.

Ken
  • 30,811
  • 34
  • 116
  • 155
0

For what it's worth, I asked an iPhone engineer about this at one of the iPhone Tech Talks. His answer was, "Yes, it's possible to use IB to create cells. But don't. Please, don't."

August
  • 12,139
  • 3
  • 29
  • 30
0

I followed Apple's instructions as linked by Ben Mosher (thanks!) but found that Apple omitted an important point. The object they design in IB is just a UITableViewCell, as is the variable they load from it. But if you actually set it up as a custom subclass of UITableViewCell, and write the code files for the subclass, you can write IBOutlet declarations and IBAction methods in the code and wire them up to your custom elements in IB. Then there is no need to use view tags to access these elements and you can create any kind of crazy cell you want. It's Cocoa Touch heaven.

David Casseres
  • 143
  • 1
  • 7