registerNib:forReuseidentifier with custom UTTableViewCell and Storyboards - iphone

I'm migrating from customizing my TableViewCells in tableView:cellForRow:atIndexPath: to using a custom UITableViewCell subclass. Here's how I done it:
First, created empty XIB, dragged UITableViewCell there and put a UILabel on top. Created a class (subclass of UITableViewCell) and in Interface Builder's properties editor set the class to MyCell.
Then, in my TableViewController, put the following:
- (void)viewDidLoad
{
[super viewDidLoad];
// load custom cell
UINib *cellNIB = [UINib nibWithNibName:#"MyCell" bundle:nil];
if (cellNIB)
{
[self.tableView registerNib:cellNIB forCellReuseIdentifier:#"MyCell"];
} else NSLog(#"failed to load nib");
}
After that I wiped out all the custom code from tableView:cellForRow:atIndexPath: and left only default lines:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"MyCell";
MyCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
return cell;
}
When I ran this, I expected to see a bunch of cells with a single label in each cell (the very label that I dropped in the middle while creating XIB). But instead I see just plain white empty cells and adding/removing the components to the XIB layout doesn't help.
I spend A DAY trying different options like setting the reuseIdentifier in Interface Builder for custom cell XIB and loading the view in tableView:cellForRow:atIndexPath:, but none helped.

...but it turned out, that the only thing that I missed, was clearing the reuseIdentifier for prototype cell in my Storyboard for this TableViewController. It seems that Storyboard initializes its views/components later that viewDidLoad called, and instead of taking my nice custom cell, xCode sets the real cell view for reusing to just plain white cell which is the standard for newly created TableViewControllers.
So again: go to your TableView properties and remove the reuseIdentifier you set before ;)
I spend so much time for this, so I thought it might help someone if I share this experience here.

Related

How to display a complex UIViewController inside another UIView Controller?

Ok, so here's the situation. I currently have a view controller called MainViewController which has a UITableView with many different cells. When I click on a cell, I want that cell to expand (grow in height) and show some "additional information". The problem is, this additional information is very complex and can contain UILabels, other UITableViews, UIWebViews and UIImageViews. Furthermore, this "additional data" requires quite a bit of computation in order to determine what exactly to display (i.e. what the UILabels say, how large the UIImageViews are). Therefore, because of the complexity of this "additional information", I'm at a loss as to how to design my program.
The "additional information" requires a lot of code, thus I don't want to just throw that code into the MainViewController. Additionally, it would be nice if there was some way to use Interface Builder to design these "additional information" views graphically rather than programatically.
Currently I have each set of additional information as its own separate UIViewController (thus allowing me to have separate classes for the data and allowing me to use interface builder) and I just segue to a new screen when a cell is selected. However, I don't want to segue to a new screen; I want all of the data that this UIView controller is showing to be shown in MainViewController. What's the best way to do this?
In summary, I currently have one UIViewController segueing to another UIViewController; however, I want the second UIViewController's content to be show in the first. If possible I would like to use some sort of Interface Builder and to separate out the logic for this second UIViewController into another class.
Details:
~ I'm developing for iOS 5 only and I'm using ARC.
~ I've never developed for iOS 4 or below before and I have never used nib files before but I would be willing to learn if required. Simple sample code would be helpful.
~ Thanks!
Same opinion as SmartWork.
You should create your custom UITableViewCell class with its xib file, with a UITableViewCell as main xib view
And in your tableView datasource, you can import it as below :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"MyViewCell";
MyViewCell *cell = (MyViewCell *)[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"MyViewCell" owner:nil options:nil];
for (id currentObject in topLevelObjects) {
if ([currentObject isKindOfClass:[UITableViewCell class]]) {
cell = (MyViewCell *)currentObject;
break;
}
}
}
[self configureCell:cell atIndexPath:indexPath]; // your own function to customize your cell.
return cell;
}
Then, in the cell xib, you can set the max height of the cell, and decide the effective height in the UITableViewDelegate class.
There are some good suggestions here, but note that loadNibNamed:owner: is a fairly expensive API to call repeatedly because it reads the nib from the filesystem each time. Instead, consider doing something like this.
Register your nib file in viewDidLoad. In Interface Builder, make sure to provide a reuse identifier for your custom cell.
- (void)viewDidLoad
{
[super viewDidLoad];
UINib *myNib = [UINib nibWithNibName:#"MyNib" bundle:nil];
[self.tableView registerNib:myNib forCellReuseIdentifier:#"MyCell"];
// Do any other stuff you need to do...
}
Then just dequeue your custom cell whenever you need it. UINib will cache the nib file in memory to avoid reloading it from the filesystem each time.
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyCell"];
// Configure cell as needed...
return cell;
}
in my opinion there is no need of using one UiviewController inside another.
u can use Uiview with nib file so u can design these "additional information" views graphically. its very easy to implement and maintain it.
Just to add to what SmartWork said, when you tap a particular cell, you can update the height of that row using the following lines of code:
- (CGFloat)tableView:(UITableView *)aTableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// set a dynamic value for the cell height depending on the state of the data in the cell
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// update the state of the data in the cells here..
// calling these below lines will change the height of the cells smoothly
[tableView beginUpdates];
[tableView endUpdates];
}
You will also need custom UITableViewCells. Look at them as simple views and add and remove any number of subviews that you need
If you are keen on using Nibs for your subviews inside the cells, you can create their nibs and connect them to your Custom TableView Cells as follows: (The subviews can be properties of your tableViewCell)
NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:#"MyTableViewCellNib" owner:self options:nil];
mySubview = [(SubView *)[nib objectAtIndex:0]retain];

UITableViewCell from a custom .XIB file doesn't create outlets

Update 2:
Long story short, I was being silly (and, in my defense, not properly educated) about this. It works now. My questions have derailed from the original topic a little bit, but that's because I wasn't understanding what was going on in my application. I would like to close the question with one last (and small) query:
I've got two labels in my customCell.xib. I want one of them (cell.label2) to sometimes contain a longer segment of text (2-3 lines). I know one way to make it all fit is to set the autoshrink property, but that shrinks to the text so it can fit on a single line. I want to preserve the original text size and expand the cell's height instead, making the text span multiple lines instead of shrinking it.
Is there a way to do this?
Update 1:
I tried a few things based on the replies below, and they got me nowhere. I am growing convinced that I am doing something fundamentally wrong, and you guys just can't think of it because it's so basic. So let's try again. I am going to changed the names a little, so they are easier to remember.
The problem seems to be in the fact that I can't create any IBOutlets or IBActions for my customCell. Right now I have 3 files that should handle this (DetailedDirectionViewCell.{h,m,xib}, but the Interface Builder doesn't allow me to create a property/outlet/reference out of my UITableViewCell object - anywhere.
Instead of copying the code here, I've provided a PasteBin entry with links to my code. As before, I've removed the less interesting methods. Take a look if you will.
http://pastebin.com/p4eADhKQ
I also have customCell.{h,m}, but those are just new Objective C class files that inherit from UITableViewCell. customCell.xib is just a cell with two labels.
So I have a couple of problems really.
First, generating a UITableView programmatically, using a custom UITableViewCell contained in a .XIB file of its own. My DirectionsViewController class is just a UITableView with programmatic cells. Tapping on one of the cells needs to present a DetailedDirectionsViewController table (in a modal way), the cell design for which sits in a DetailedDirectionsViewCell.xib file. The problem is, I can't create an IBOutlet for the UITableViewCell from the nib file - anywhere. Dragging the File's Owner icon/outlet doesn't offer to create anything. Which, after 5 hours of struggling, means that I can't present my detailed view.
The second problem involves adding a navigation bar to the DirectionsViewController, but let's leave that alone for now.
Here are some methods you might find helpful:
//inside DirectionsViewController
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)
{
DetailedDirectionsViewController *vc = [[DetailedDirectionsViewController alloc] initWithStyle:UITableViewStyleGrouped];
vc.instruction = [turnList objectAtIndexPath:indexPath.row];
[self.tabBarController presentModalViewController:vc animated:YES];
}
//inside DetailedDirectionsViewController
- (UITableViewCell *) tableView:(UITableView *) cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"directionsCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
[[NSBundle mainBundle] initWIthNibNamed:#"DetailedDirectionsViewCell" owner:nil options:nil];
cell = self.tableViewCell;
self.tableViewCell = nil;
}
//here I configure the custom cell
return cell;
}
I don't think the rest of the methods are of interest, because they are either working as expected, or are pretty much the default ones.
To sum up:
DirectionsViewController - essentially a UITableViewController with custom cells. No .xib file
DetailedDirectionsViewController - detailed information about the entries from DirectionsViewController. Cells here should come from a .XIB file, but that's broken.
DetailedDirectionsViewCell - this is the custom cell. I can't set its File's Owner.
Ok.. You do not create IBOutlet connection from File's Owner. Have a look at a screenshot. You create IBOutlet from CustomCell's view(with Red Arrow).
Looking after your code just follow these steps.
1) Goto CustomCell.h file. As you are saying customCell.xib has two UILabels(assume label1 & label2) you gonna have to declare properties and create outlets in CustomCell.h file and in .m file synthesize and release it. Refer this code screen of mine.
2) Now in CustomCell.xib, select view of CustomCell not File's Owner(File's Owner should inherit from NSObjectonly) go to Identity Inspector(Marked with Red Ellipse) and select the corresponding Customcell class (marked with Red rectangle).
3) Right click your customcell's view and make connections to labels. And save it..
4) In your DirectionsViewController.m you have this UITableView's delegate method cellForRowAtIndexPath. Change it like this :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CustomCellIdentifier = #"CustomCell";
EditProjectCustomCell *cell = (EditProjectCustomCell *)[tableView dequeueReusableCellWithIdentifier: CustomCellIdentifier]; // typecast to customcell
[cell setSelectionStyle:UITableViewCellSelectionStyleBlue];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"EditProjectCustomCell" owner:self options:nil];
for (id oneObject in nib)
if ([oneObject isKindOfClass:[EditProjectCustomCell class]])
cell = (EditProjectCustomCell *)oneObject;
[cell setSelectionStyle:UITableViewCellSelectionStyleBlue];
}
cell.label1.text=[someArray1 ObjectAtIndexPath: indexPath.row];
cell.label2.text=[someArray2 ObjectAtIndexPath: indexPath.row];
return cell;
}
this delegate method is gonna be called as many times as you returned value in numberOfRowsInSection DataSource method. Every time cell.label will be blank and you will add data to label by calling this line. so no need to create label each time as you did between line 79-90 here. http://pastebin.com/Vd4PKjTu
cell.label1.text=[someArray ObjectAtIndexPath: indexPath.row];
Creating custom cell means you create UI(i.e. .xib),interface & implementation (.h,.m file) for UITableViewCell by yourself and adopt them in your class's (i.e. DirectionsViewController.m) cellForRowAtIndexPath delegate method.
To load a custom cell, I've used this code (test it and it's working)
static NSString *CustomCellIdentifier = #"CustomCommentIdentifier";
detailedDirectionsViewCell *cell = (DetailedDirectionsViewCell *)[tableView dequeueReusableCellWithIdentifier:CustomCellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"DetailedDirectionsViewCell" owner:self options:nil];
for (id oneObject in nib)
{
if ([oneObject isKindOfClass:[DetailedDirectionsViewCell class]])
{
cell = (DetailedDirectionsViewCell *)oneObject;
}
}
}
and make sure that you changed the cell class nib file as the following:
Click on the files owner, change the type of it to NSObject (this for not to confuse when connect the Outlets.
Remove any view you have in the nib file, then, drag and drop a UITableViewCell component.
Change the super class of the component to the Cell class, in your case, change the cell from UITableViewCell to DetailedDirectionsViewCell.
Connect the outlet.
This should work, let me know if you have any question.
To answer your last question in Update 2, did you consider using a text field instead of a label? You should be able to disable editing so the user can't change what is displayed. If you need it to be a label, in the Attributes Inspector for the label, there is a property called "Lines" under the "Label" drop down that you can adjust. I'm not sure how to access that property programmaticly, but a quick Google search should help you out there.
In my case I had two different views for one cell class. And when I connected outlets to .h file they were connected only to the first view representation, not the second.
So to ensure that your outlets are really connected go Xcode me menu and open View -> Utilities -> Show FileConnection inspector, then make sure that your view's outlets are really connected.

iOS 5 Storyboard Custom Cell Crash: UITableView dataSource must return a cell

This is either an XCode bug, or me missing a crucial rule here.
Update:
- What's the chance of this being a weird bug in XCode/Storyboard?
Situation:
iOS 5, Storyboard
This is the storyboard setup: http://i.imgur.com/T5WyD.png
Another screenshot of the full setup: http://i.imgur.com/1tVuz.png
TableViewController with Custom Cell, cell has reusable identifier "NewCell"
in "cellForRowAtIndexPath" I basically have:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"NewCell"];
return cell;
This throws an exception:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath:'
Things I have already tried:
I setup a new project from scratch, TabBarController, with a TableViewController and a Custom Cell, all wired up in Storyboard, set a reusable cell identifier. Used the same code as above, worked perfectly. I don't know why it didn't work with the storyboard above...
I have a feeling it has something to do with the tree I built there, which is a TabBarController, loading a NavigationController, loading a TableViewController, providing a few items, one is clicked, which loads another TableViewController, which is unable to work with the custom cell, somehow.
Important:
- The issue is that the Storyboard should make sure that:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"NewCell"]; will never return NIL (unlike without the Storyboard/iOS4). But, mine is nil. And I can't, for the hell of it, figure out what's happening.
Heyo, I just had this problem today and figured out a few possible causes. To link a UITableView as a subview of a ViewController in Story board check that you have done these steps.
In your "ViewController.h", add <UITableViewDataSource> to your list
of protocols
#interface ViewController : UIViewController
<UITableViewDataSource>
you might want to add <UITableViewDelegate> as well but I didn't.
In your "ViewController.m" set up your Table view data source
functions
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [yourCells count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NewCell *cell = (NewCell *)[tableView
dequeueReusableCellWithIdentifier:#"NewCell"];
if (cell == nil) {
NSLog(#"Cell is NIL");
cell = [[CustomCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
return cell;
}
In Storyboard, connect the UITableView's "Data Source" and
"Delegate" references to the ViewController.
In Storyboard, give the Prototype Cell the Identifier "NewCell"
If these are done, I believe it should work.
Hope this helps =)
I always use custom cells in the storyboard like this:
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
return cell;
Make sure the number of rows/sections is at least one. And another thing to check is that you set the UITableViewController to your custom class name in the storyboard.
Did you double check the cell prototype identifier is "NewCell"?
Your setup as Tabbar->navigationcontroller->tableview is perfectly fine. I
am using this layout all the time.
verify also there's no useless runtime attributes anywhere in this
viewcontroller/tableview/cell.
your prototype looks a bit weird there - you added a UIButton to the cell as subview? why?
To solve this problem add the following just above your dequeueReusableCellWithIdentifier statement:
static NSString *CellIdentifier = #"NewCell";
making sure the identifier matches the prototype cell in Storyboard
It maybe late to answer this but i bet this will fix all your problems

Creating custom UITableViewCell's within a Storyboard

Wanting to create a static menu (IOS 5) and attempting to create custom cells within the storyboard to then load onto the grouped tableview.
I've created the outlet
#property(nonatomic,strong) IBOutlet UITableViewCell *labelCell;
The ViewController class is set to the proper TableViewController and I've connected the custom cell to this outlet.
I also have the delegate and datasource set up.
I've got
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
return self.labelCell;
}
I'm sure there is a ton wrong with this, but I'm just trying to display one cell and go from there. There does not seem to be any examples of doing custom cells within the IB through the storyboard. I can still use the old way of creating a xib file and loading it in the mainBundle but I just want to stay up to date I guess.
but with what I have above i get a crash when I load this view controller. SIGABRT
Here is what I've learned about how you get cells for your table when using the storyboard. When you drag a UITableView into your view, it comes with a prototype cell already set as a subview. To use this prototype cell, set a unique reuse identifier in the attributes inspector, and then use the same identifier to dequeue the cell in your cellForRowAtIndexPath: method. I leave out the code for creating a cell from scratch if the dequeue call returns nil; I don't think it can happen. So just dequeue the cell, configure it with the usual UITableViewCell methods, and return it.
But you can also create custom subclasses of UITableViewCell. Just set the class name in the storyboard's class identity inspector, and drag whatever elements you want from the Objects palette into your cell. Then create IBOutlet properties for them in your subclass's code files, and hook them up to the cell in the storyboard in the usual way. This is so much better than having to do it all in code!
And finally, you can have more than one kind of cell in your table. Just drag UITableViewCell objects from the palette into the table, and give each one a unique reuse identifier in the attributes inspector. In your cellForRowAtIndexPath: method, choose the type of each cell and you can have a very flexible table view.
If you have set your UITableView to be using 'Static Cells' in the storyboard, you don't need to implement any of the UITableViewDataSource methods and you can modify the cell directly in Interface Builder. For a single label cell, select the cell and change it's type to 'Basic'. You can now edit the cell just like you would any other view object.
This tutorial was helpful to me. You can reference whatever object you need through the tag.
In the Storyboard drag on a UIImageView or UILabel etc. and set the tag to 100 (whatever you want) then in your - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath use the tag to reference it.
Here is the example code in the tutorial, just remember to set the tags in the storyboard:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Display recipe in the table cell
Recipe *recipe = [recipes objectAtIndex:indexPath.row];
UIImageView *recipeImageView = (UIImageView *)[cell viewWithTag:100];
recipeImageView.image = [UIImage imageNamed:recipe.imageFile];
UILabel *recipeNameLabel = (UILabel *)[cell viewWithTag:101];
recipeNameLabel.text = recipe.name;
UILabel *recipeDetailLabel = (UILabel *)[cell viewWithTag:102];
recipeDetailLabel.text = recipe.detail;
return cell;
}

How to make a UITableViewCell with different subviews reusable?

I have a UITableView in which I display, naturally, UITableViewCells which are all of the same class, let's call it MyCell. So I have one MyCell.xib, one MyCell.h and one MyCell.m.
Unfortunately, this cells do contain one subview, which holds varying content, e.g. a train subview and a car subview. So if the UITableView is in need of a new cell, it's always a MyCell but sometimes it contains a train subview and sometimes a car subview.
Now, here is my problem: How to make MyCell properly reusable? The cell itself is reusable as intended (In the .xib I defined it's identifier) but it's subview has to be created again and again for every cell. My first idea was to change the identifier of MyCell depending on it's content but unfortunately, reuseIdentifier can't be changed on runtime.
I could, however, implement my own - (NSString *) reuseIdentifier {} which I guess would work, though I wouldn't consider it great style. Is there a better way to do this?
Many thanks in advance!
EDIT: I realize I need to add that the subviews are stored in their own classes/xibs to keep their code seperated.
Instead of adding subviews to cells I'd suggest that you create for every kind of cell your own class. If you have the kinds: train, car, bike, boat and airplane I would create five subclasses.
As I understand Apple the reuse mechanism with the identifier is just for that case: different types of cells get their own identifier, not every single cell a special one. Just to point how I interprete the whole thing.
In Apple's Table View Programming Guide for iOS / Characteristics of Cell Objects, the 3rd paragrpah delivers some insight into the meaning of the reuse identifier.
I've written myself a small TableViewCellFactory class which makes my life easier to create cells with the interface builder and have those in my app within minutes.
First of all a small example on how to use cellForRowAtIndexPath and the factory as well as setting content for a cell.
I create a fresh cell with the factory which needs the tableView so it can handle the reuse logic. Next thing is to let a method fill in the content for the cell. In this case it's a cell which shows a video clip with some text.
Data Source delegate method and helper
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)anIndexPath
{
VideoClipTableViewCell *cell = [TableViewCellFactory videoClipTableViewCellWithTableView:aTableView];
[self configureVideoClipCellWithCell:cell andIndexPath:anIndexPath];
// code to decide what kind of cell not shown, but it could be here, just move the model
// access code from the configure cell up here and decide on what you get
return cell;
}
Next comes the data source helper to put content into the cell. Get the content from my model array and set the properties. Note, this does everything by reference, nothing is returned.
- (void)configureVideoClipCellWithCell:(VideoClipTableViewCell *)aCell andIndexPath:(NSIndexPath *)anIndexPath
{
VideoClip *videoClip = [videoClips objectAtIndex:anIndexPath.row];
aCell.videoTitleLabel.text = videoClip.title;
aCell.dateLabel.text = videoClip.date;
// more data setting ...
}
TableViewFactory
This class consists mainly of convenience methods and some boilerplate methods to do the real work.
// Convenience static method to create a VideoClipTableViewCell
+ (VideoClipTableViewCell *)videoClipTableViewCellWithTableView:(UITableView *)aTableView
{
return [self loadCellWithName:#"VideoClipTableViewCell" tableView:aTableView];
}
// method to simplify cell loading
+ (id)loadCellWithName:(NSString *)aName tableView:(UITableView *)aTableView
{
return [self loadCellWithName:aName
className:aName
identifier:aName
tableView:aTableView];
}
// method with actually tries to create the cell
+ (id)loadCellWithName:(NSString *)aName
className:(NSString *)aClassName
identifier:(NSString *)anIdentifier
tableView:(UITableView *)aTableView
{
UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:anIdentifier];
if (cell == nil) {
UINib * nib = [UINib nibWithNibName:aName bundle:nil];
NSArray * nibContent = nil;
nibContent = [nib instantiateWithOwner:nil options:nil];
for (id item in nibContent) {
if ([item isKindOfClass:NSClassFromString(aClassName)]) {
cell = item;
}
}
}
return cell;
}
I've thrown out the whole error and exception handling just to keep the example short. If someone is interested I'd add the code.
Some important things about the usage is:
The connected class name, the reuse
identifier and the nib name are all
the same so a cell can be created
with only one string constant, else
the long loadCellWithName has to be
used.
Don't forget to set the reuse identifier in interface builder.
The nib should contain only one TableViewCell (can be changed with some coding though)
Don't set outlets of the File's Owner, use those of the tableViewCell
Set the class identity of the cell to a corresponding class which should be created foremost
Look at the screenshot
Thoughts on subclassing own custom cells
It's actually easy to subclass your own cell, add a few properties to it, make them available in IB with outlets, choose the new extended class in IB for your nib file.
The main problem is interface itself. It's not easily done to have different kinds of cells based on a custom cell in interface builder. The first approach would be to copy the nib file, rename it and use it with all the existing references and link the new ones to differing outlets. But what happens if the base cell has to be changed? Going through all kinds of inheriting cells could be a tedious task.
I just stumbled across Custom views in Interface Builder using IBPlugins on Cocoa with Love. It's a nice tutorial how to extend the components Library in IB. Our custom base cell could become an item in the library and become the template we've been looking for. I think this approach is the right way to choose. Still, looking at necessary steps, it's not just done within 5 minutes.
Interface builder is a helpful tool, allowing us to rapidly create views, but when it comes to reusability through subclassing, there are big steps necessary to create maintainable views. A pity.
Creating the views with code only I think one is better off with subclassing if it comes to more than one level of inheritance or many ancestor classes for just one base view.
EDIT
On the other hand, Apple warns about excessive use of subviews in a cell:
However, if the content of a cell is
composed of more than three or four
subviews, scrolling performance might
suffer. In this case (and especially
if the cell is not editable), consider
drawing directly in one subview of the
cell’s content view. The gist of this
guideline is that, when implementing
custom table-view cells, be aware that
there is a tradeoff between optimal
scrolling performance and optimal
editing or reordering performance.
Right now any approach has its drawbacks and advantages:
Too man subviews will hit
performance, easily done with IB
Drawing with code will result in a
hard to maintain code base but will
perform better
Skipping IB makes
subclasssing of template cell classes
easier
Hierarchy through subclassing
difficult to achieve with IB with nib
files
There are a couple of different ways to do this. You need a way to access that subview and reset or change it on reuse.
You could subclass UITableViewCell with your own class of cell that has a property for the train or car view. That way you could access and change that view when the cell is reused.
Assign a different identifier to each type of cell:
`
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CarCellIdentifier = #"CarCell";
static NSString *TrainCellIdentifier = #"TrainCell";
if(indexPath == carCellNeeded) {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CarCellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CarCellIdentifier] autorelease];
[cell addSubview:carView];
}
} else if(indexPath == trainCellNeeded){
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:TrainCellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TrainCellIdentifier] autorelease];
[cell addSubview:trainView];
}
}
return cell;
}
Or assign a special tag to that sub view you are adding and when the cell comes back around again to be reused you can access that specific subview by its tag.
I would add both custom subviews to the nib and connect them to an outlet. And depending on the content I would hide one of them when you configure the content of your cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = /* load from nib */
}
if (/*indexPath conditionForTrainCell*/) {
cell.trainSubview.hidden = NO;
cell.carSubview.hidden = YES;
// configure train cell
}
else {
cell.trainSubview.hidden = YES;
cell.carSubview.hidden = NO;
// configure car cell
}
return cell;
}
the simplest is to make a custom UITableViewCell subclass and create a xib for it. Set the root view in the xib as an uitableviewCell and set its class to your UITableViewCell subclass. Set the file owner class as your TableViewController subclass and add all the subviews you want to it. Then you can simply:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString * CellIdentifier = #"cellIdentifier";
TableViewMessageCell * cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([TableViewMessageCell class])
owner:self
options:nil] lastObject];
}
Message * message = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.message = message;
return cell;
}