What's a Cell Identifier good for? - iphone

I wonder why I need a Cell Identifier in a UITableView... like this:
static NSString *cellIdentifier = #"Cell";
what's that needed for? Example?

A UITableView needs to be able to smoothly and quickly display rapidly-changing data, and sometimes cells have additional code to build the cell itself, using Core Graphics or the like.
A UITableView can only display a small number of cells at once, while there may be many more "rows" contained in the datasource. In order to reduce processing and memory usage, apple has provided the dequeueReusableCellWithIdentifier method. This allows the tableview to re-use an already instantiated cell that has dropped off the view, if available.
The UITableView can contain different types of cells. For example you might have a tableview where some rows had an associated image and others, did not, with different cell layouts. Or you have different cell types depending on some other upstream application setting. The CellIdentifier tells the dequeue method which type of cell you are looking to reuse, so you don't receive the wrong type of cell.
static NSString *CellIdentifier = #"Cell with image";
UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:CellIdentifier];
If a cell can't be found to reuse, then it must be created, ie:
if(cell == nil) ....
The confusion arises as many applications only use a single type of cell for a given tableview, so the CellIdentifier does not change.
Theres a good writeup from Apple at:
https://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/TableView_iPhone/TableViewCells/TableViewCells.html
See also:
Use two different cell identifier on same table view
and: http://www.digitalhobbit.com/2009/12/19/a-useful-uitableview-cell-creation-pattern/

It's used as a key for caching of cells, for example:
- (UITableViewCell *)tableView:(UITableView *)tableView_ cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString * CellIdentifier = #"MyCell1";
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
...
Then a different table might use a different identifier...

Cell Identifiers are good if you need to reuse cells to conserve application memory. For example, if you have many cells in your application, instead of releasing the cell once the user scrolls past it, the cell is simply modified to contain the info of new cells that you are creating. This conserves space since it isn't necessary to allocate thousands of cells if the user is only looking at 10 at a time. The identifier is what the system uses to check if there are cells with that identifier in existence already. If there are some, it uses them. Otherwise, it must allocate new space and create new objects.

Related

Best practice in UITableView with different kind of UITableViewCell

her's what we need to do:
the data source of the tableView core data.
a tableView that contain 5 different type of custom tableViewCell.
every tableViewCell is completely different from others.
some tableViewCell will have a progressBar.
we have 3 solutions:
use a unique tableViewCell with a unique reuse identifier.
use a unique tableViewCell with 5 reuse identifier.
use 5 tableViewCell and 5 reuse identifier.
we test the 1st solution, it's ok in iphone 5/ iphone 4S, but in iphone 4 it's slooooooow (and we need to support the 3GS too ...).
The question: which solution will be better? or if you have other solution it will be great.
the favor: can you explain how the reuse identifier work (in details please :) ), like when the first cells are allocated, when they are reused (with 1 and with different reuse identifier), when they are desallocated ... ?
thank you.
If all cells are completely different (layout) you should init each cell type with a unique reuse identifier to benefit from the performance advantages of dequeing cells later. The tableview will init as many cells as necessary (depending on the number of sections and rows in each section) for filling its bounds no matter which reuse identifier you assign. Cells are cached as they disappear from the visible area of the tableview. That means at least one cell of each reuse identifier that has gone off the screen is kept in memory for reuse. When the tableview is scrolled and another row is needed it will ask cellForRowAtIndexPath to provide a cell for this row. When there's no cell with a specified resuse identifier in the queue a new cell is created, subviews are initialized and layout / arrangement of subviews is done. If there's a cell with the specified resuse identifier in cache the tableview takes this cell "as is" and customizes it just according to the specifications you provide in cellForRowAtIndexPath like assign a different image to an imageView or set a label's text which is much more "inexpensive" than creating a completely new cell. You can check that by only setting a label's text in your custom cell's initWithStyle. If you don't modify the text after calling dequeueReusableCellWithIdentifier in cellForRowAtIndexPath the label's text will be the same in every cell dequeued with the same reuse identifier. Also complex backgrounds (e.g. with gradients) will be reused and don't need to be redrawn everytime a new cell appears on the screen. Assigning ONE reuse identifier to all different types of cells, reusing the cells would cause nearly the same effort as creating a new cell for each row (assuming each cell type equally spread). Cells in the queue will be deallocated when the tableview is deallocated. Hope this helps you understanding the concept of reusing tableview cells.
This was my solution, and it works ok on 3gs, now depends how complex is your cell and how many things you do # [cell load] method. Try to avoid for/while loops there.
if(indexPath.row == 0){
static NSString *CellIdentifier = #"HeadCell";
kHeaderCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
[cell load];
return cell;
}
if(indexPath.row == 1){
static NSString *CellIdentifier = #"HistCell";
kHisoryCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
[cell load];
return cell;
}
...and so on

Xcode 4.2 "cell reuse indentifier in nib (Cell) does not match the identifier used to register the nib (ThisCell)"

I'm trying to use the new StoryBoard feature of Xcode 4.2 and keep getting this error when using a Table View with a custom cell.
cell reuse indentifier in nib (Cell) does not match the identifier used to register the nib (ThisCell)
I've set the class of my custom cell to my custom UITableViewCell class and set the Identifier to "ThisCell" in IB.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"ThisCell";
//TableViewCell *cell = (TableViewCell*)[tableView dequeueReusableCellWithIdentifier:#"ThisCell"];
TableViewCell *cell = (TableViewCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
return cell;
I have no idea where "Cell" is coming from. Any ideas? I tried loading a custom cell in another project, and it seems to work fine, I just can't find any documentation on this error to find out what I messed up in the current project.
Thanks
The error message states that in your nib the cell is identified as "Cell"—you need to look in the xib file that defines your custom cells for configuration of the Cell Reuse Identifier, and set that to ThisCell... Or just change ThisCell to Cell in this code.
Take a look at following 2 places in your project. And make sure they both string identifiers are same.
The problem is as #Duncan Babbage described. It happens when there is some type of conflicts in the reuse identifier for the same instance. (In my case, I defined two different reuse identifiers in both Storyboard and code and they were conflicting each other )
you can also look for "Cell" outside the .xib file, specifically in all project files.

Prevent the UITableView from reusing UITableViewCell?

Is it harmful to comment out the code that reuses the cell in a UITableView?
// if (cell == nil)
// {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
// }
Thanks.
EDIT: Based on the reply of #jrturton, I decided not to do that.
It will increase memory use and affect scrolling performance. If you've only got 10 cells, it probably doesn't make any difference. With 1,000, it will be very noticeable.
You will also need to comment out the dequeue call to the tableView.
I have to ask, though - why are you concerned about this? There may be a better solution to your problem.
Based on your comment, you might still want to reuse a cell per row - otherwise when something is scrolled off screen and returned, you would have to recreate it. You can incorporate the indexPath row and section into the cell reuse string to facilitate this:
NSString *cellIdentifier = [NSString stringWithFormat:#"cell%d.%d",indexPath.section,indexPath.row];
Unless you have a very specific/unique reason, you should use the mechanisms for queueing and de-queing cells that are provided in the template method cellForRowAtIndexPath. This will keep your memory footprint as low as possible and keep scrolling smooth.
Note that you have the ability to modify aspects of a cell each time it is presented, so if you'd care to share your motivation in trying to avoid reusing cells, perhaps my advice would be different.

When do custom table view cells dealloc?

I was under the impression that table view cells never got dealloced until the app crash because you are able to resuse them. But when I was profiling my table view, I realized that something was calling dealloc on my custom cell. What exactly dealloc's custom cells and can I stop it?
Using the common "reuse" pattern:
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellId = #"Foo";
UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:cellId];
if (!cell) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId]
autorelease];
}
// update the cell content
return cell;
}
The table view creates as many cells as required to fill the its frame height.
When disappearing from screen they are removed from the view and put in the reuse queue, thus not deallocated.
All the cells are dealloc'd when the tableView is dealloc'd and some cells may be dealloc'd when you number of rows changes (say you had 20 cells before and only 2 after update).
You might be tempted to get rid of cells reuse but you would lose all the magic done behind to keep a low memory footprint and to have a smooth scrolling experience.
The semantics of the dequeueReusableCellWithIdentifier: selector are opaque (as far as I know). If you want complete control of your table view cells, don't use that selector to get a cell: just construct a new one or use your own pool for reuse.
Table views create and shed cells as needed. This is to allow for very long lists to be scrolled through without storing them all in memory. A virtually infinite scrollable list is possible. A convenient way to retain your custom cells for the table view's lifespan is to add them to a mutable array upon creation. Before creating, look in the array to see if it already exists.

Items mixed up after scrolling in UITableView

When I scroll in my UITableView, the cells become mixed up.
What am I doing wrong?
This is my method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
[cell insertSubview:[itemArray objectAtIndex:indexPath.row] atIndex:indexPath.row];
return cell;
}
Update
It now works by using cell.contentView, but now when I select an item, the selected one is overlayed with the content of a different cell...
TechZen's advice here is correct. It's clear from your code that you've misunderstood insertSubview:atIndex. I suspect that you probably also need a better understanding of when tableView:cellForRowAtIndexPath: does and doesn't called.
Unfortunately you've gotten some bad advice from sagar here, which may only confuse you further, especially because it may appear to work at first, but it will kill your scrolling performance and memory usage. For his benefit and yours, let me try to clarify tableView:cellForRowAtIndexPath: and the reuse identifier concept.
The key to understanding tableView:cellForRowAtIndexPath: and the reuse identifier is to understand that building a UITableViewCell is expensive. Consider all the things you need to do:
Allocate a cell
Allocate the cell's subviews.
Define the layout of the subviews within the cell.
Add the subviews to the cell.
Configure properties of the subviews such as font sizes, colors, text wrapping, resizing behaviors, etc.
Configure properties of the cell, such as accessory images, etc.
Define the specific text and/or images that you want the cell to display.
When we create a table, we usually want the cells to have the same basic configuration. They'll typically have the same number of subviews, in the same positions, using the same fonts, etc. In fact, the only thing that usually needs to vary from one cell to the next is item 7 in the list above, the text and images displayed by the cell.
Steps one through six are quite expensive (especially the memory allocation), so it would kill our scrolling performance if we were to go through those steps for every cell we created, only to throw that cell away when it scrolls off the screen. It would be better if we could save the cell when it scrolls off the screen, and then just tweak its contents and reuse it for the next cell that we need to display.
Apple recognized the need for this cell reuse optimization, so they built a mechanism for it right into UITableView. When a cell scrolls off the screen, UITableView doesn't throw it away. Instead it looks at the cell's reuse identifier string, and puts the cell into a special buffer associated with that identifier. The next time you call dequeueReusableCellWithIdentifier: with that same identifier, UITableView will pull the cell out of its buffer and hand it back to you for reuse. This cell still has all the same subviews, in the same configuration as before, so all you need to do is step 7 in our list. Simply update the cell's text and/or images, and it's ready to go.
When you use this mechanism correctly, you'll only allocate one cell for each visible row, plus one for the buffer. No matter how many rows you have in your table, your memory usage will stay low, and your scrolling will be as smooth as butter.
Sagar recommended that you use a different reuse identifier for each row. Hopefully you can see why this is a bad idea. When each cell scrolls off the screen, the table view will look at the cell's identifier, see that it's unique, and create a new buffer for that specific row. If you scroll through 10,000 rows, your table view will end up with 10,000 buffers, each dedicated to a single cell. Your scrolling will be unnecessarily slow while you create 10,000 cell objects, and your app will probably run out of memory before you get to the bottom of the table.
So go ahead and keep your common cell identifier. Inside the if (cell == nil) { } block, put all the setup code that would be common for all cells. Beneath that block, put only the code that populates the contents that are unique to each row. To access custom subviews whose contents you want to change per row, you can use -[UIView viewWithTag:], or better yet, create a subclass of UITableViewCell, and expose your custom subviews as properties of your subclass.
I think your problem here is that you are applying your row logic to the view hierarchy inside a cell instead of to the cells themselves.
This line:
[cell insertSubview:[itemArray objectAtIndex:indexPath.row] atIndex:indexPath.row];
Takes a view from an array and adds it to the cell's subviews at a particular index.row of the cell's existing subview stack. It does nothing to make sure the proper view is inserted in the proper cell itself. If you never remove the views from the previous iteration you will just see all these views stacking up within the individual reused cells.
At the very least, you need to remove all the previously added cell subviews before adding the most one. You should also only add subviews to the cell's contentView view and not to the cell itself.
So:
[[cell.contentView.subviews objectAtIndex:0] removeFromSuperview];
[cell.contentView addSubview:[itemArray objectAtIndex:indexPath.row]];