20/11: UITableViewCell and XIB / NIB
Category: General | Posted by: JohnHaselden
The UITableViewCell is great for laying out information to be displayed in a UITableView (obviously), but there doesn't seem to be a simple way of getting the NIB you have created within the Interface Builder and your UITableViewCell. Thankfully, it's quite simple.
Create your UITableViewCell
And add a layoutSubviews method within the implementation section; ie -
Now, create a new empty XIB file and call it whatever you want - I will use MyTableViewCell to match the Obj-C class. Open this XIB up using Interface Builder and look at the two items in the doc window: File's Owner and First Responder. In the inspector, change the File's Owner class to be UIViewController if this isn't already set. From the library, drag a Table View Cell into the doc window after the First Responder. Change the class of this new Table View Cell to be MyTableViewCell. Now associate the main view with the Table View Cell by clicking on File Owner and then on the Connections Inspector on the entry for 'view', drag a line from the empty circle at the end of this line onto the 'My Table View Cell' entry in the doc window.
OK, now let's set up the label. Double click on the 'My Table View Cell' entry in the doc window. Drag a label from the library into the view of 'My Table View Cell' that should have been opened. Within the outlets group in the connections inspector for the table view cell, drag a line from the circle at the end of the 'exampleLabel' line onto the label we just added to the table cell. Save the view.
That's all great, but we haven't yet told our code how to load up the XIB for this table cell view yet. We need to have a view controller conforming to the UITableViewDelegate and UITableViewDataSource protocols (which we won't go into here). Within the '- (UITableViewCell *)tableView:(UITableView *)TableView cellForRowAtIndexPath:(NSIndexPath *)indexPath' method, we need to create our UITableViewCell implementation.
Obviously this is a pretty basic example, but the code follows the same principle for all other elements you may wish to add to your UITableViewCell.
Create your UITableViewCell
@interface MyTableViewCell : UITableViewCell {
IBOutlet UILabel *exampleLabel;
}
And add a layoutSubviews method within the implementation section; ie -
- (void)layoutSubviews {
exampleLabel.text = @"This is my example!";
/*
You may also have a little more control over your font here also
*/
exampleLabel.font = [UIFont boldSystemFontOfSize:12.0];
}
Now, create a new empty XIB file and call it whatever you want - I will use MyTableViewCell to match the Obj-C class. Open this XIB up using Interface Builder and look at the two items in the doc window: File's Owner and First Responder. In the inspector, change the File's Owner class to be UIViewController if this isn't already set. From the library, drag a Table View Cell into the doc window after the First Responder. Change the class of this new Table View Cell to be MyTableViewCell. Now associate the main view with the Table View Cell by clicking on File Owner and then on the Connections Inspector on the entry for 'view', drag a line from the empty circle at the end of this line onto the 'My Table View Cell' entry in the doc window.
OK, now let's set up the label. Double click on the 'My Table View Cell' entry in the doc window. Drag a label from the library into the view of 'My Table View Cell' that should have been opened. Within the outlets group in the connections inspector for the table view cell, drag a line from the circle at the end of the 'exampleLabel' line onto the label we just added to the table cell. Save the view.
That's all great, but we haven't yet told our code how to load up the XIB for this table cell view yet. We need to have a view controller conforming to the UITableViewDelegate and UITableViewDataSource protocols (which we won't go into here). Within the '- (UITableViewCell *)tableView:(UITableView *)TableView cellForRowAtIndexPath:(NSIndexPath *)indexPath' method, we need to create our UITableViewCell implementation.
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = @"MyTableViewCell";
MyTableViewCell *cell =
(MyTableViewCell *)[inTableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
UIViewController *c = [[UIViewController alloc]
initWithNibName:@"MyTableViewCell" bundle:nil];
cell = (MyTableViewCell *)c.view;
[c release];
// code here to set whatever values in MyTableViewCell
}
return cell;
}
Obviously this is a pretty basic example, but the code follows the same principle for all other elements you may wish to add to your UITableViewCell.
Comments
JohnHaselden wrote:
Yeah, sorry about the anti-spam questions - the blog software only allows me to set up one set of questions for all the blogs on the site and I had the Java one first. I guess I need to make them a little more generic!
This foible had me stumped for quite a while I have to admit and I had spent a great deal of time laying out some of my cells 'by hand' in code rather than using the builder.
However, having come across from Java, I cannot describe how great it is to have a tool like Interface Builder at all as it is miles ahead of anything in Java-land.
This foible had me stumped for quite a while I have to admit and I had spent a great deal of time laying out some of my cells 'by hand' in code rather than using the builder.
However, having come across from Java, I cannot describe how great it is to have a tool like Interface Builder at all as it is miles ahead of anything in Java-land.
14/01 20:51:37
JohnHaselden wrote:
In this example no, but it would be fairly trivial to implement by either implementing another init method (eg: initWithNibName:(NSString *) reuseIdentifier:(NSString *) bundle:nil) and then setting _reuseIdentifier appropriately or overriding - (NSString *) reuseIdentifier to return your reuse identifier.
12/03 16:17:26
Dimitri wrote:
Great this works :) but there is something i didnt get with :
cell = (MyTableViewCell *)c.view;
[c release];
cell is receiving c.view (without any retain), then the controller "c" is released, so its properties, included "view" (c.view). cell is also released before "tableView:cellForRowAtIndexPath:" returns...Is there a problem with this...or maybe i just misunderstood this solution :)
cell = (MyTableViewCell *)c.view;
[c release];
cell is receiving c.view (without any retain), then the controller "c" is released, so its properties, included "view" (c.view). cell is also released before "tableView:cellForRowAtIndexPath:" returns...Is there a problem with this...or maybe i just misunderstood this solution :)
28/04 09:17:38
JohnHaselden wrote:
Yes and no. Release does not happen immediately so you have a period of time before the auto release pool will pick this up and actually reclaim the memory. In practice I have never had any issues with this method in the 7 or so projects that use it.
However, if you really feel worried that this is going to leave you with a bug in your code, use [c autorelease] instead.
However, if you really feel worried that this is going to leave you with a bug in your code, use [c autorelease] instead.
28/04 09:35:34
Brett Shellhammer wrote:
I wish to add my thanks here as well ... I have looked for this for a couple of days ... so Thank you.
One question. I wanted to set up a DateFormatter for my cell to use and I had been doing that in the override of the initWithFrame ... which is never called when using a XIB file ... I tried overriding the initWithNibFile method but the compiler did not like that ... where should I put that initialization code ? Sorry if this is a basic question.
Thanks in advance.
One question. I wanted to set up a DateFormatter for my cell to use and I had been doing that in the override of the initWithFrame ... which is never called when using a XIB file ... I tried overriding the initWithNibFile method but the compiler did not like that ... where should I put that initialization code ? Sorry if this is a basic question.
Thanks in advance.
21/05 21:16:56
JohnHaselden wrote:
You could put the initialization within the layoutSubviews method and use lazy initialization:
if (!dateFormatter) // perform initialization here
Hope this helps!
if (!dateFormatter) // perform initialization here
Hope this helps!
22/05 09:10:05
Brett Shellhammer wrote:
Thanks for the reply ... that is basically what I did, I was just wondering if there was a better way.
One other snag I hit is the Row Height. I basically have two different table view cells that I use this method for
... and you guessed it ... they have different row heights.
The row height of my first MyTableViewCell is 80 and the second 60 but the row height that is set in the parent table view nib is the one that is controlling it for all. I know I can override the heightForRowAtIndexPath method and set it manually for each section ... but then I have to make sure I synch that value back to the height of my cell that I created in Interface Builder, which is OK but not as elegant as I would like ? Is there a better way to get the height for MyTableViewCell from the nib and use it directly ?
Thanks again for your help, I appreciate it.
One other snag I hit is the Row Height. I basically have two different table view cells that I use this method for
... and you guessed it ... they have different row heights.
The row height of my first MyTableViewCell is 80 and the second 60 but the row height that is set in the parent table view nib is the one that is controlling it for all. I know I can override the heightForRowAtIndexPath method and set it manually for each section ... but then I have to make sure I synch that value back to the height of my cell that I created in Interface Builder, which is OK but not as elegant as I would like ? Is there a better way to get the height for MyTableViewCell from the nib and use it directly ?
Thanks again for your help, I appreciate it.
22/05 18:21:19
JohnHaselden wrote:
Not within the NIB that I am aware of.
What I tend to do with variable size cells is to provide a static method to perform the calculation for the height of the cell:
+ (CGFLoat) heightForCellWithMessage:(NSString *)message;
The implementation of this takes the message that you wish to display and return how much space you need to display this message.
Within the static method, the code is similar to:
CGSize fSize = [message sizeWithFont:[UIFont boldSystemFontOfSize:17.0] constrainedToSize:CGSizeMake(280, 100) lineBreakMode:UILineBreakModeWordWrap];
return fSize.height;
Within your layoutSubviews method, you can then re-use this method to set the size of your UILabel frame.
What I tend to do with variable size cells is to provide a static method to perform the calculation for the height of the cell:
+ (CGFLoat) heightForCellWithMessage:(NSString *)message;
The implementation of this takes the message that you wish to display and return how much space you need to display this message.
Within the static method, the code is similar to:
CGSize fSize = [message sizeWithFont:[UIFont boldSystemFontOfSize:17.0] constrainedToSize:CGSizeMake(280, 100) lineBreakMode:UILineBreakModeWordWrap];
return fSize.height;
Within your layoutSubviews method, you can then re-use this method to set the size of your UILabel frame.
22/05 20:19:59
Dave Perry wrote:
It's also worth mentioning that the tag property of a UIView (in this case a UITableViewCell) is invaluable when creating UITableViewCells in Interface Builder.
Instead of messing about with possibly lots of IBOutlets for a complicated UITVC with many views, you can access each view via a tag.
Instead of messing about with possibly lots of IBOutlets for a complicated UITVC with many views, you can access each view via a tag.
04/06 15:07:09
JohnHaselden wrote:
Dave, how does that work when it comes time to dealloc the UITVC? I've never used the tag side of things so it might be a useful insight for both myself and others.
10/06 11:34:16
Sam T wrote:
You just saved me some time!
This way I can have my "design" guy set up the cell in Interface Builder just the way he wants it, and I don't have to then translate that into code, I can just grab his TableViewCell and use it.
Thanks.
This way I can have my "design" guy set up the cell in Interface Builder just the way he wants it, and I don't have to then translate that into code, I can just grab his TableViewCell and use it.
Thanks.
27/07 20:57:09
Sean wrote:
Thank you! I have spend quite some time looking for a decent way to accomplish this very thing and this worked quite well!
24/03 22:34:46
bob wrote:
Quite nice actually, using UIViewController as a factory is smart. The casts are a drag but Ive seen way more code thrown at this problem on other blogs than is warranted, nice job!
06/07 07:36:10
JohnHaselden wrote:
Perhaps if you weren't so very lame Mikhail you would be able to work it out for yourself that way you would actually learn something.
05/09 19:55:56



Justin wrote:
Apple's setup with the Interface Builder is ridiculous.. I can't believe you have to jump through this hoop to get a UITableViewCell you designed in the builder just to be used in a table. Having to use the view property of a UIViewController to pass the object? Bad.
By the way, the anti-spam questions to leave a comment are ridiculous.