I have a UItableview with cells. Some cells have uilabels and some have uibuttons. The UIbuttons are created whenever the first character in an array is "^". However, the uibuttons repeat when i scroll down (appearing over the uilabel).. and then multiply over the uilabels when I scroll up. Any clues why?
The content of a cell should not be set inside the if (cell == nil)
. Only the structure (layout) should be defined. The content should be set after the check:
CellIdentifier = @"TableCell";
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
// setup everything that's the same for every cell
// (e.g. add controls to the cell)
}
// setup everything that's different in each cell (set text, images, ...)
What's happening here is that the cell is being "prepared" and cached, so it won't have to be recreated for every cell. If you set the content of the cell inside the if (cell == nil)
, you're storing the contents of the cell in the cache.
Actaully every time the section is scrolled and rows are loaded, all the labels and ui components are added to the cell at run time, you need to clear all the views which are added in the cell, everytime when your cell is dequeued and is not nil, See the following example:
static NSString *CellIdentifier;
CellIdentifier = [NSString stringWithFormat:@"Cell%d%d",indexPath.section,indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
/*
* THIS IS FOR OS VERSION 2.2 THE OLDER VERSION
* cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
*/
} else {
for (UIView* tempView in cell.contentView.subviews) {
[tempView removeFromSuperview];
}
}
UITableView does not retain one UITableViewCell for each logical row. It only retains the number of cells immediately necessary to display the rows currently on screen. In practice this means that only 9-11 default height cells exist at any one time.
The table view creates the illusion of an arbitrarily long table by calling the table view's datasource and requesting either a new cell for the row or to reuse an existing cell. This reuse is called "dequeueing" the cell. After you've scrolled past the intial displayed rows, you no longer create new cell instances but instead use the same ones over and over again.
Your problem arises because once you set a cell to have UIButton, you never subsequently remove the button. Once a cell has a button in it, it will display that button forever even when it is used to display a logical row that should not have a button. Because the same cells are used over and over again to display different rows, scrolling rapidly causes all the cells to show buttons.
To correct your problem you need to remove the button when you want to display a label and remove the label when you want to display a button.
Better yet, you should define two subclasses of UITableViewCell, one for each display. When you have a row with a button return the button subclass cell and when you have one with a label return the label subclass. That way you can move all the cell configuration logic to the cell class and only set the data in the datasource methods. Using subclasses to encapsulate custom logic results in much neater and cleaner code that is easier to debug and maintain.
You are using reusable cells, so in cellForRowAtIndexPath: you have to check - is there a button or label in cell? If so, remove them, alloc-init a new one and add it as subview. To do that use tags, it is very comfortable.