I was wondering if some one can please reply me with if the UITableView queue gets flushed when UITableView reloadData is called.I am trying to do so and this isnt helping me.Any suggestions?
if you look at the header file for UITableView, you can see that there is a private NSMutableDictionary (iVar) called "_reusableTableCells". This is a dictionary with cell reuse identifiers as key and with array with cells that are currently offscreen as value.
If you want to manually flush queued cells in way that may change with the implementation, you can do so in ugly manner, like so:
NSMutableDictionary *cells = (NSMutableDictionary*)[self.tableView valueForKey:#"_reusableTableCells"];
[cells removeAllObjects];
Hope this helps...
Once the table is loaded, the cells are reused. Reloading the table does not flush the queue. reloadData calls - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
In this method the if(cell==nil) condition is present so that cells are not flushed once they have been loaded into memory, and therefore reused.
To get around this you reset your cells before applying the correct information.
cell.detailTextLabel.text = #"";
cell.accessoryType = UITableViewCellAccessoryNone;
Or if you are using an accessoryView
cell.accessoryView = nil;
Also take a look at this example. UITableView not updating correctly when scrolling.
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
In the below code I want to perform some DB actions when a cell is deleted, therefore I need to send my Server information about the cell being deleted. If I remember correctly cellForRowAtIndexPath should never be called directly, However I cannot think of any other way to get cell info in the below method, so my question is:
Is it acceptable to call cellForRowAtIndexPath manually below:
[tableView cellForRowAtIndexPath:indexPath]);
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[localGlobalNotifications removeObjectAtIndex:indexPath.row];
[notificationTableView beginUpdates];
[notificationTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationLeft];
[self postLeaveRequest];
NSLog(#"Row is : %#", [tableView cellForRowAtIndexPath:indexPath]);
[notificationTableView endUpdates];
}
}
To clarify: I understand that I can invoke a delegate call of cellForRowAtIndexPath by calling reloadData, what I'm trying to do is access the cell being deleted within commitEditingStyle. I'm not trying to reload my tableView, instead I want to get a reference to the cell being deleted. - Is it acceptable to obtain a reference to said cell by calling cellForRowAtIndexPath directly?
There's nothing wrong with asking the TableView to give you the cell, just as you do in your sample code.
Here's the documentation for the return value:
An object representing a cell of the table or nil if the cell is not visible or indexPath is out of range.
If you're annotating the cell with 'model' data then I think you're breaking the MVC pattern. Your view doesn't need to know about the model data in this way, and so querying the view to make a database change will make life difficult you in the future (readability, extensibility and reusability for example)
You would be better off having your DB metadata stored in a collection such as an NSArray - or an NSArray of NSArrays.
Then you could get all the data you need with something like:
id modelData = myModel[indexPath.section][indexPath.row];
Yes you can use cellForRowAtIndexPath: as they have also used in apple docs or you can create an array of your cells then you can delete it from there and reflect it in your database.
You can call cellForRowAtIndexPath: method using following line, here it will automatically calls all delegate and datasource methods.
[tableView reloadData];
or
[tableViewObjct reloadData];
Hope this solves your problem!
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.
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]];
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.