From the official documentation:
The reuse identifier is associated with a UITableViewCell object that the table-view’s delegate creates with the intent to reuse it as the basis (for performance reasons) for multiple rows of a table view. It is assigned to the cell object in initWithFrame:reuseIdentifier: and cannot be changed thereafter. A UITableView object maintains a queue (or list) of the currently reusable cells, each with its own reuse identifier, and makes them available to the delegate in the dequeueReusableCellWithIdentifier: method.
http://developer.apple.com/iphone/library/documentation/UIKit/Reference/UITableViewCell_Class/Reference/Reference.html#//apple_ref/occ/instp/UITableViewCell/reuseIdentifier
I don't understand this. Well, I understand the basic idea, I think, that you create UITableViewCells, and try to reuse as many as you can instead of making new ones (or something like that). But what exactly decides whether or not a cell is reusable? If I've got two identical (visually) cells, but with different texts (well I suppose they aren't entirely identical), can they both have the same identifier? Or should they have different ones? Or in what situation are you supposed to use different identifiers?
Can anyone clarify or link to a place where it is?
Ok, this is how I believe it works:
Using dequeueReusableCellWithIdentifier for the tableView, you can greatly speed things up. Instead of instantiating a lot of cells, you just instantiate as many as needed, i.e. as many that are visible (this is handled automatically). If scrolling to an area in the list where there are "cells" that haven't got their visual representation yet, instead of instantiating new ones, you reuse already existing ones.
You can try this yourself by doing this:
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
NSLog(#"new one");
}
else
{
NSLog(#"old one");
}
Remember, you only want dequeueReusableCellWithIdentifier to return a cell if it is applicable. So if a cell is going to be reused, make sure it is correct for the situation. That's what reuseIdentifiers are for. Usually, you will only need one. But there might be a list that uses several different kinds of cells, and in that case, you'd have to keep them separate by providing different reuseIdentifiers. Otherwise you might end up getting a cell that you treat as some other kind of cell (for example, UITableView cell instead of the custom one you wanted).
So basically, as I understand it, use different reuseIdentifiers for different kinds of cells, where kind means class. If you only use standard cells, you probably only need one reuseIdentifier.
This design pattern is known as object pooling.
Just to add some things to quano's otherwise very good answer: (I tried to add this as a comment, but it was too long!)
Even reuse identifiers can be omitted in developing, although this must be done in very specific circumstances. If you have a table view of 6-7 cells, and each one is different, you may find that creating a new cell with nil as the identifier may be preferable.
Having a reusable cell means that in each time the cellForRowAtIndexPath is called, you must check the cell, initialize it if there is no reusable cell, and outside of the init scope you must explicitly iterate through all possible indexpaths and set the values for each label explicitly depending on what kind of cell you have! So, in a table view with 10 dinstinct cells, you will have to take care of creating the cell if nil, and filling it up depending on what you created.
Therefore, in this case, it's preferable in terms of code maintenance to initialize each cell with nil identifier (since it's not going to be reused anyway) and fill each cell's info appropriately without worrying about reusing it.
UITableView is like having a cell pool for each reuseIdentifier, so that it recycle the cell
I like this video from http://oleb.net/blog/2014/05/scrollviews-inside-scrollviews/
http://im.ezgif.com/tmp/ezgif-3302899694.gif
Related
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
What is the scope of the table view cell's reuse identifiers — are they shared within one table view instance or within all the table views that use the same reuse identifier?
Eg, I have a FooTableViewController and a BarTableViewController, both of them have a tableView and both of them use #"Cell" identifier in tableView:cellForRowAtIndexPath, but the cell properties/styling are different. The question is - will those cells be reused across table views or not?
They are never shared between instances.
A UITableView object maintains a queue (or list) of the currently reusable cells, each with its own reuse identifier, and makes them available to the delegate in the dequeueReusableCellWithIdentifier: method.
(from [UITableViewCell reuseIdentifier] docs)
Emphasis on "UITableView object".
However, you should make your cell identifiers more descriptive. A different identifier for every cell type you are using. Your code will be more readable.
They will only be used in a single UITableView, they will never be shared. Each UITableView has its own "pool" of reusable UITableViewCells
A picture is worth a thousand words, and I am guessing that the pictures posted below clearly convey the problem I am facing now.
Here's a little summary:
I create a tableView, and each tableViewCell has a textField as a subview. I scroll up and down, and my view gets messed up. I am guessing it is because of Cell Reusability. But I need help with this.
Note: This problem is not because of the buttons or the uitextView at the bottom of the screen. If I do not have them, and I have only the first three sections, the textFields get shifted and it messes up a the textFields of several cells. Also notice the textField shift in image 4 compared to other images. The code is pasted here http://www.pastie.org/2203340
**P.S.:** I have added the solution to the problem at the end of the question.
This is my normal view
This is when I scroll down (look below)
After several scrolls Up and Down... (look below)
After several more scrolls... (look below)
SOLUTION :
I really thank #caleb and others for pointing me in the right direction and helping me fixing this bug. For those people, whom are also facing the same problem, I thought I should provide a short and sweet answer.
Use UITableViewCells with different CellIdentifiers. That would make sure that the same cell does not get called.
Your guess that it has to do with cell reuse is correct. The first thing I noticed is that you're using the same cell identifier for all your cells. That means that all cells are considered the same, and you should assume that everything about a cell will have to be configured every time. It'd be easier to use different identifiers for different types of cells, but I'll let you chew on that much for a bit...
Anyway, the root of your problem is this code:
if (section ==3){
cell =nil;
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
if (indexPath.row ==0){
cell.backgroundColor = [UIColor clearColor];
cell.contentMode = UIViewContentModeScaleToFill;
[cell.contentView addSubview:self.footerView];
}
}
For this particular cell, you're setting the background color to clear, adding the footerView, etc. But you don't set the background color for any of the other cells, so as soon as this particular cell happens to be reused in a different position, you're going to get the clear background and other modifications in that position.
Now it'll probably be easier to see why you might want to use different cell identifiers for different types of cells. If you use a different identifier for your clear cells than you do for your white ones, you don't have to worry about clear cells being recycled as white cells and vice versa. You should use a different identifier for each "cell type", where "cell type" is defined by all the cell attributes that you don't want to have to configure each time you reuse a cell.
You're doing a lot of work in your -reuseTableViewCellWithIdentifier:withIndexPath: method that ends up making the cells not very reusable. Instead, you should identify each different "type" or "style" of cell that you want to display independant of the particular information that's displayed in them. For example, all the cells in your first three sections appear to be about the same: they're all similar to the UITableViewCellStyleValue1 standard cell style, except that the detail label is left empty and a text field is added to the right side. Use the same reuse identifier for all these cells, then. -reuseTableViewCellWithIdentifier:withIndexPath: really shouldn't care about the index path -- it should just create and return a cell in the required style as determined by the supplied reuse identifier. Leave the work of setting the text of the fields in those cells to the -tableView:cellForRowAtIndexPath: method.
I notice that you're storing the index path and a pointer to the text field when you create each cell. Neither of these is going to help at all if you're reusing cells.
If you're never going to add more cells to the table than the ones you've shown, it may be simpler for you to simply skip the whole reuse thing. Reusing cells gives a marked performance improvement when scrolling through a table with dozens or hundreds of cells, but if you'll never have more than ten or fifteen cells, and half of those are on the screen at any given time, the advantage of reusing cells isn't that great. Nevertheless, I'd encourage you to spend the time to get your head around the idea; sooner or later you're going to want to create a table with many rows.
I've experienced this before too. Try creating a new cell for each type. This way cells that contain text fields aren't reused for the title only cells.
One way that should fix this is to set all cells to nil in your cellForRowAtIndexPath method. This makes sure that each is drawn freshly with no chance of something being overlayed like in your pictures. The downside is that it's extra work for the system, not being able to reuse cells like it wants to do
Is there a way to tell a UITableView to preload all rows?
The tableView is supposed to show several comments (up to 80 comments).
So my CommentCell uses a Setter to adapt the cell to a specific comment.
-(void)setComment:(Comment *)newComment {
if (newComment != comment) {
[comment release];
comment = [newComment retain];
/*
* set the cells view variables here
*/
}
}
This specific setter takes quite a bunch of processing resources and scrolling gets kinda laggy.
I am using a comment-specific reuseIdentifier instead of a static cellIdentifier when calling
dequeueReusableCellWithIdentifier:
in order to assure, that "newComment" equals the old "comment".
And in fact this does work great when scrolling over cells which have already been loaded.
But when scrolling through the comments for the first time, it still lags like hell.
Which leads me to my question:
Is there a way to tell the tableview to preload all cells? (which I doubt)
or
Do I have to implement my own cache instead of relying on "dequeueReusableCellWithIdentifier:"?
Keep in mind that your comment specific reuseIdentifier could be what is causing everything to go slow (or at least, it isn't helping). The reason we use reuseIdentifier for UITableViewCells is because if you try to allocate a new cell every time you need one it isn't as performant as if you can just reuse one that was already made.
I'd recommend pre-computing your comments so you can just set properties of your cells and reusing cells after they scroll off the tableview.
No, you have to preload your data in your dataSource. Just put everything you need in an array and fill the table's cells from that array of preloaded objects.
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]];