I'd like to create custom UITableViewCell using an XIB, but I'm not sure how to recycle it using UITableViewController's queueing mechanism. How can I accomplish this?
Folks, this question was intended to be self answered as per the FAQ, although I love the awesome responses. Have some upvotes, treat yourself to a beer. I asked this because a friend asked me and I wanted to put it up on StackOverflow. If you have anything to contribute, by all means!
If you're using iOS 5 you can use
[self.tableView registerNib:[UINib nibWithNibName:#"nibname"
bundle:nil]
forCellReuseIdentifier:#"cellIdentifier"];
Then whenever you call:
cell = [tableView dequeueReusableCellWithIdentifier:#"cellIdentifier"];
the tableview will either load the nib and give you a cell, or dequeue a cell for you!
The nib need only be a nib with a single tableviewcell defined inside of it!
Create an empty nib and add the table cell as the first item. In the inspector, you can add the reuseIdentifier string in Interface Builder.
To use the cell in your code, do this:
- (UITableViewCell *)tableView:(UITableView *)_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *reuseIdentifier = #"blah"; //should match what you've set in Interface Builder
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
if (cell == nil)
{
cell = [[[NSBundle mainBundle] loadNibNamed:#"YourTableCellNib" owner:nil options:nil] objectAtIndex:0];
}
//set up cell
return cell;
}
There is another method where you create an outlet for your cell and load the cell nib using the controller as the file's owner, but honestly this is much easier.
If you want to be able to access the subviews you've added to the cell in the nib, give them unique tags and access them with [cell viewWithTag:x];
If you want to be able to set custom properties on the cell, you'll need to create a custom UITableViewCell subclass, then just set that as the class of your nib in InterfaceBuilder and cast the UITableViewCell to your custom subclass when you dequeue it in the code above.
To set up a custom UITableViewCell using a XIB, you have to do several things:
Set up an IBOutlet in your header
Configure the table view cell in Interface Builder
Load the XIB inside of tableView:cellForRowAtIndexPath:
Configure it like any other cell
So... Let's set up an IBOutlet in the header file.
#property (nonatomic, retain) IBOutlet UITableViewCell *dvarTorahCell;
Don't forget to synthesize it inside the implementation file.
#synthesize dvarTorahCell;
Now, let's create and configure the cell. You want to pay attention to the Cell Identifier and the IBOutlet as shown below:
Now in code, you load up the XIB into your cell as shown here:
Notice that the Cell Identifier in Interface Builder matches the one shown in the code below.
Then you go ahead and configure your cell like any other.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"YUOnlineCell" owner:self options:nil];
cell = dvarTorahCell;
dvarTorahCell = nil;
}
//configure your cell here.
Just note that when accessing subviews, such as labels, you now need to refer to them by tag, instead of by property names, such as textLabel and detailTextLabel.
Here how you can do:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
YourCustomeCell *cell = (YourCustomeCell *)[tableView dequeueReusableCellWithIdentifier:CellClassName];
if (!cell)
{
NSArray *topLevelItems = [cellLoader instantiateWithOwner:self options:nil];
cell = [topLevelItems objectAtIndex:0];
}
return cell;
}
Where cellLoader in .h is defined as follow:
UINib *cellLoader;
and in .m is istantiated as follows (for example during initialization):
cellLoader = [[UINib nibWithNibName:CellClassName bundle:[NSBundle mainBundle]] retain];
and CellClassName is the defined in .m as follows (is also the name for your xib).
static NSString *CellClassName = #"YourCustomeCell";
Do not forget to use the string CellClassName also in your xib created cell.
For further info I suggest you to read this fantastic tutorial creating-a-custom-uitableviewcell-in-ios-4.
Hope it helps.
P.S. I suggest you to use UINib because is an optimized method to load xib files.
Related
I have a project where I need to use a custom UITableViewCell. I'm designing the cell as a prototype in storyboard and it looks fine there. I assign the prototype to my custom UITableViewCell subclass, give it the same reuse identifier I'm using in my UITableView and link the UILabel on the prototype cell to an IBOutlet in my UITableViewCell subclass.
When I call it from the UITableView the cell is created and if I add labels and buttons in the code of that class (create them with a CGRect and all) they all work but the labels I've added in the storyboard never show up.
I don't understand how my subclass can be called and created successfully but its layout and subviews from the storyboard don't seem to exist as far as my app is concerned. What am I doing wrong?
Here's my cellForRowAtIndexPath code
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
// Configure the cell...
return cell;
}
I've run into this issue before, and in my case, the problem was that the auto-generated code for the view controller included a call to:
[UITableView registerClass:forCellReuseIdentifier:]
I would suggest checking for and removing any calls to the above, or to
[UITableView registerNib:forCellReuseIdentifier:]
and trying your original code again.
acreichman, add casting in cellForRow and put an NSLog in you cell's awakeFromNib to see if you get there. Let me know...
Your cellForIndexViewPath should look like this, to create a simple custom cell with label,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SimpleTableCell";
SimpleTableCell *cell = (SimpleTableCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"SimpleTableCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
cell.nameLabel.text = [tableData objectAtIndex:indexPath.row];
return cell;
}
Make sure that you have made all connections well, set datasource and delegate to table and then set the “Identifier” of the custom cell to "MyTableViewCell" in “Attributes Inspector” like this,
For storyboard:
Add "MyTableViewCell" instead of "SimpleTableCell" as shown in above screenshot.
I am making a custom ui table view cell. I know i can use loadNibNamed method to use .xib
of the cell but that causes scrolling to be slow when my I have too much data in it .
I want to use UI nib because its lot faster than loadNibNamed method for loading custom cell .xib file .
static NSString *cellIdentifier = #"PostStreamCell";
PostStreamCell *cell= [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"PostStreamCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
UINib *cellNib = [UINib nibWithNibName:#"MemberCell" bundle:nil];
[cell.collectionView registerNib:cellNib forCellWithReuseIdentifier:#"MemberCell"];
}
cell.textlabel.text =#"xyzabc123";
I tried using code below in above "if "block but failed to use it. Any help would be appreciated.
UINib *cellNib = [UINib nibWithNibName:#"PostStreamCell" bundle:nil];
[cell registerNib:cellNib forCellWithReuseIdentifier:#"PostStreamCell"];
Create your xib file with a UITableViewCell as the top-level object. This is called Cell.xib
Create a UINib object based on this file
Register the UINib with the table view (typically in viewDidLoad of your table view controller subclass).
use the following line in viewDidLoad:
[self.tableView registerNib:[UINib nibWithNibName:#"Cell" bundle:nil] forCellReuseIdentifier:#"Cell"];
Then, in cellForRowAtIndexPath, if you want one of the cells from the nib, you dequeue it:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
This either creates a new instance from the nib, or dequeues an existing cell.
You only need to register the Xib once, so this regiserNib code should go within the viewDidLoad method of your class file
UINib *cellNib = [UINib nibWithNibName:#"PostStreamCell" bundle:nil];
[self.tablview registerNib:cellNib forCellWithReuseIdentifier:#"PostStreamCell"];
Then within your cellForRowAtIndexPath method of your tableview
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifier = #"PostStreamCell";
PostStreamCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
cell.textlabel.text =#"xyzabc123";
}
This will work for you as you need it.
Cheers
Edit in response to PostStreamCell error*
Please make sure your custom UITableViewCell xib has the 'PostStreamCell' identifier, which you can set in this part of the IB
Please note the xib is a UITableViewCell object.
Update - pic showing where to check custom class files associated with xib
Within the Utilities of the IB, please make sure your custom UITableViewCell uses your custom class files PostStreamCell. Please check they're shown in this part of the IB.
Also within your UIViewController class files, (where you have the UITableView methods etc) make sure to
#import "PostStreamCell.h"
I've successfully been able to use registerNib to define a UITableViewCell's UI within an XIB that only has one UITableViewCell prototype within it.
In the interest of encapsulation, would it be possible to have an XIB with more than one UITableViewCell in it and still be able to load the proper cell using registerNib?
If so, how would one identify the desired cell prototype within the XIB in code?
I'm currently doing this in the tableView's VDL which works fine:
[self.tableView registerNib:[UINib nibWithNibName:#"LogoCell" bundle:[NSBundle mainBundle]]
forCellReuseIdentifier:CellId];
Is there a way to specify which cell prototype within the XIB we want to use?
Thanks in advance.
In the interest of encapsulation, would it be possible to have an XIB with more than one UITableViewCell in it and still be able to load the proper cell using registerNib?
No.
Actually, it is possible. If you have some way of distinguishing the views in your XIB (tags, custom classes, etc), then you don't even need to register the nib – all you have to do is this:
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger cellIndex = 12345;
NSString *cellIdentifier = #"MyAwesomeCell";
// dequeue cell or, if it doesn't exist yet, create a new one from the nib
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell)
{
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"MyNibWithLotsOfCustomCells" owner:self options:nil];
NSUInteger index = [topLevelObjects indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
// if you distinguish the views by class
return [obj isMemberOfClass:NSClassFromString(cellIdentifier)];
// if you distinguish the views by tag
return [(UIView*)obj tag] == cellIndex;
}];
cell = topLevelObjects[index];
}
// etc.
}
It's important to use dequeueReusableCellWithIdentifier: and not dequeueReusableVellWithIdentifier:forIndexPath: (the difference is explained in this thread).
I've read all the relevant other questions on this topic and tried the fixes, none of which have worked. My app crashes/hangs to the extent that I have to force quit Xcode in order to restart working, when dequeueReusableCellWithIdentifier: is called.
It makes no difference if I use dequeueReusableCellWithIdentifier:, or dequeueReusableCellWithIdentifier:forIndexPath: , and I HAVE set the class with registerClass:forCellReuseIdentifier: , as you can see in the code below.
Registering the class in my ViewController:
#implementation LWSFlavourMatchesViewController
-(void)viewDidLoad
{
[super viewDidLoad];
_flavourMatchesView = [LWSFlavourMatchesView flavourMatchesViewWithDataSource:self.flavourMatchesDataSource andDelegate:self.flavourMatchesDelegate];
self.tableView = _flavourMatchesView;
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"flavourCell"];
}
And trying to dequeue cell in tableView:cellForRowAtIndexPath in my dataSource:
#implementation LWSFlavourMatchesDataSource
// other methods...
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *flavourCellIdentifier = #"flavourCell";
NSString *currentSelectedFlavour = [self.flavourWheel selectedFlavour];
UITableViewCell *tableViewCell = [tableView dequeueReusableCellWithIdentifier:flavourCellIdentifier forIndexPath:indexPath];
if(tableViewCell == nil)
{
tableViewCell = [[UITableViewCell alloc ]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:flavourCellIdentifier];
}
[tableViewCell.textLabel setText: currentSelectedFlavour];
return tableViewCell;
// return [UITableViewCell new];
}
If I remove all other code but un-comment out return [UITableViewCell new]; then the app does not crash. What is it about my dequeuing that is causing this problem?!
I refactored your tableview delegate. You do not need to check if the cell is nil because you registered the class with [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"flavourCell"];.
I made your cellIdentifier static. But to remove the duplication on the registerClass function may you make a #define REUSE_IDENTIFIER #"flavourCell".
If this is still slow, than is the [self.flavourWheel selectedFlavour]; the cause. Check out the instruments tutorial for performance improvements: http://www.raywenderlich.com/23037/how-to-use-instruments-in-xcode
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *flavourCellIdentifier = #"flavourCell";
NSString *currentSelectedFlavour = [self.flavourWheel selectedFlavour];
UITableViewCell *tableViewCell = [tableView dequeueReusableCellWithIdentifier:flavourCellIdentifier forIndexPath:indexPath];
[tableViewCell.textLabel setText: currentSelectedFlavour];
return tableViewCell;
}
try removing the class registration:
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"flavourCell"];
You shouldn't need to register the class if you are instantiating a generic UITableViewCell class cell from a storyboard
Another cause for this error can be-
invalid nib registered for identifier (Cell) - nib must contain exactly one top level object which must be a UICollectionReusableView instance
I had a UIView in my xib instead of a collectionViewCell. Of course, if you have multiple top level objects in the .xib, the same crash will show. Hope this helps.
Take care that if your cell is an object in a XIB, and you are using something like :
[self.tableView registerNib:[UINib nibWithNibName:#"cell_class_name" bundle:nil] forCellReuseIdentifier:#"cell_reuse_name"];
to register the cell, be sure the identifier in the attributes inspector in Interface Builder is correct, in this case #"cell_reuse_name".
If the identifier isn't the same, you may be stuck with an odd situation where creating new cells from the nib each time, i.e.
NSArray *objects = [bundle loadNibNamed:#"cell_nib_name"
owner:nil
options:nil];
cell = (UITableViewCell *)[objects safeObjectAtIndex:0];
seems to work fine, but trying to use
[tableView dequeueReusableCellWithIdentifier:#"cell_reuse_name"];
crashes.
In practice it's often easiest to use the same name for the XIB, custom class, and reuse identifier. Then if there are issues, you can just make sure they are all the same.
I had completely different issue. Mismatch between String/XIB based localization. It did help to enable/disable+remove unneeded localizations.
Try this:
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"NIB_NAME" owner:self options:nil];
If it hangs, there's something wrong with your XIB (like in my case).
Try this:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:nil];
if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"flavourCellIdentifier"]; }
I am using a tableview inside a uiviewcontroller inside a XIB. Because the controller is not a UItablecontroller, I cannot configure the cell directly. If I drag in a tableview cell, i can configure that with the cell identifier etc,but the XIB doesn't use my tableview cell. How do I join them up?.I would rather not use code as this seems counterintuitive to the workflow.
If you are using Storyboards you can directly add custom cells within the VC and configure them. But if you are using xib's you have to either create a custom cell class and load them programmatically or create a custom class with custom cell in separate xib and then load the custom cell from xib, or even you just create a xib with a simple custom table cell and load from xib. Refer apple Docs on UITableView
So as I understood, you want a custom cell in your code. If you are not using storyboards, try using this. Here I have created a custom class for custom cell and added the custom cell in xib. I added a separate xib, dragged and dropped UITableViewCell in xib(remove any views if present). Now click on the cell you just added, in the identity inspector change the class name to that of the custom class you created. Select files owner and change its class to the name of class where you need your custom cell. (This tells thats your cell will be used in that particular class where the tableview's delegate and datasource is connected).
Now in the class where you want your cell, add a IBOutlet of type UITableViewCell. Connect this to the cell you created in xib.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"CustomCell";
YourCustomCell *cell = (YourCustomCell*)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell)
{
[[NSBundle mainBundle] loadNibNamed:#"YourCustomCell"
owner:self
options:nil];
cell = self.yourOutletCell;
self.yourOutletCell = nil;
}
}
And if you are using xib's to load and if you have more than one xib's of custom cell, you can load them via the xib name like this
NSArray *topLevelObjects;
topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"YourCustomCell" owner:self options:nil];
for (id currentObject in topLevelObjects)
{
if ([currentObject isKindOfClass:[UITableViewCell class]])
{
cell = (YourCustomCell *) currentObject;
break;
}
}
If it is a static Cells, you can point to this cell with Outlet and then return it in the cellForRowAtIndexPath