I've seen a number of people having a similar issue, but either their solution did not help, or it was too different.
My problem is, I have a customized UITableViewCell, custom size, image and content. When I scroll up or down and then back again, the text within some of the cells disappears. This seems to be happening randomly.
I have read about the "dequeue"-issue but either I got it wrong or it doesnt fall into my case....
Anyways, heres the code for my Cells:
- (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.selectionStyle = UITableViewCellSelectionStyleNone;
cell.imageView.image = [[UIImage alloc] initWithContentsOfFile:
[[NSBundle mainBundle] pathForResource:
[[shopItems objectAtIndex:indexPath.row] name] ofType:#"jpg"]];
UITextField *temp= [[UITextField alloc] initWithFrame:cell.frame];
temp.text = [[shopItems objectAtIndex:indexPath.row] name];
temp.textColor = [UIColor whiteColor];
temp.textAlignment = UITextAlignmentCenter;
UIImageView *cellView = [[UIImageView alloc] initWithImage:[[UIImage alloc] initWithContentsOfFile:
[[NSBundle mainBundle] pathForResource:#"smalltabs_wide_bg" ofType:#"png"]]];
[cellView addSubview:temp];
cell.backgroundView = cellView;
return cell;
}
Do you have any(!) ideas on this?
The possible problem is that you add UITextField and UIImageView to your cell each time you (re-)use the cell. Your code should look like
{
...
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
// Insert all subviews to cell here
}
// setup cell's subviews here
...
}
And also mind all memory leaks in your code - when you create something with alloc, you must release it somewhere.
use reusableCell method
read this discussion
http://discussions.apple.com/thread.jspa?threadID=2075838&tstart=1035
One problem is that you are not releasing your temp UITextField, this will lead to a memory leak and could be causing the problems.
Sharing my experience:
Had similar problems. I have many tables in the app, and in one of them data would randomly disappear, and eventually the entire table would go away.
The problem for me was that I was dequeueing cells, giving all of them the same "unique" id. So several tables were sharing the same id, and I believe they were conflicting and messing up the cells I was looking at.
Giving each table it's own unique identifier solved the problem for me. (dah!)
(posted this here too, might be a duplicate : UITableView custom cell images disappear after scrolling.)
Related
To sum it up, I'm facing 2 problems, the 1st is that time to time some cells disappear from the tableview and reappear randomly. The 2nd is a scrolling performance issue on iPhone 4 and below.
1st problem :
I am displaying a simple list of elements in an UITableView by adding a subview the [cell contentView]. Most of the time it works well, but sometimes when i scroll (and really randomly) a cell disappears.
Because this cell which "disappeared" is reused, the reused cell will also be "blank" when I scroll. And randomly again a cell can reappear/disappear while scrolling.
Here is my code for the cellForRowAtIndexPath: method :
- (UITableViewCell *)tableView:(UITableView *)_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [_tableView dequeueReusableCellWithIdentifier:CellIdentifier];
LBSelectorChampionViewController *item = [self itemAtIndexPath:indexPath];
if (cell == nil) {
NSLog(#"CELL NIL");
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
[[item view] setTag:99];
[[item champName] setTag:50];
[[item champImage] setTag:51];
[[item buttonFavorite] setTag:52];
[[cell contentView] addSubview:[item view]];
}
UILabel *championLabel = (UILabel*)[[[cell contentView] viewWithTag:99] viewWithTag:50];
LBFavoriteChampionButton *buttonFavorite = (LBFavoriteChampionButton*)[[[cell contentView] viewWithTag:99] viewWithTag:52];
UIImageView *championImage = (UIImageView*)[[[cell contentView] viewWithTag:99] viewWithTag:51];
// Text
[championLabel setText:[item displayValue]];
[championImage setImageWithURL:[NSURL URLWithString:[item imageUrl]]];
// Fav button
[buttonFavorite setCurrentChampion:item];
if ([[self favoritesChampions] containsObject:item]) {
[buttonFavorite setSelected:YES];
[buttonFavorite setIsFavorite:YES];
} else {
[buttonFavorite setSelected:NO];
[buttonFavorite setIsFavorite:NO];
}
return cell;}
}
I've tried to log everything, I checked if "item" could be sometimes null and it's never the case. But when a cell disappears, the [[[cell contentView] subviews] count] is equal to 0, which normally should be 1.
I really don't understand what's happening here. I tested it on real devices + simulator and it happens with both of them.
2nt problem :
My other "problem" is that the scrolling of the tableview is not as smooth as i would expect it to be on the iPhone 4 (tested on iOS 6 and iOS 7, same result :-( ). I'm using SDWebImage to load images asynchronously and to cache them, I'm reusing cells, what could I do to improve the performances ? I tested on an iPhone 4S and had no problem, scrolling is very smooth.
Am I doing something wrong ? Any ideas about one of my problem ?
Thank you for taking the time to answer me.
EDIT : 1st attempt to solve the problem
Trying to customize cell with a custom UITableViewCell subclass :
- (UITableViewCell *)tableView:(UITableView *)_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"LBListChampionCell";
LBListChampionCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil){
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"LBListChampionCell" owner:self options:nil];
cell = [topLevelObjects objectAtIndex:0];
}
LBSelectorChampionViewController *item = [self itemAtIndexPath:indexPath];
[[cell championName] setText:[item displayValue]];
[[cell championLogo] setImageWithURL:[NSURL URLWithString:[item imageUrl]]];
return cell;
}
I wasn't able for the moment to reproduce the "disappear" bug (sometimes it takes a moment to reproduce it...), but there is no improvement at all concerning the performances :(. Even while just setting a dummy text : [[cell championName] setText:#"Test"]; (and commenting the item part) the scrolling is still not really smooth.
Any ideas ?
EDIT 2 : Best solution (thanks to rdelmar)
Create a subclass of UITableViewCell and load the nib in viewDidLoad :
- (void)viewDidLoad
{
...
UINib *nib = [UINib nibWithNibName:#"LBListChampionCell" bundle:nil];
[[self tableView] registerNib:nib forCellReuseIdentifier:#"LBListChampionCell"];
}
- (UITableViewCell *)tableView:(UITableView *)_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
LBListChampionCell *cell = [tableView dequeueReusableCellWithIdentifier:#"LBListChampionCell"];
LBSelectorChampionViewController *item = [self itemAtIndexPath:indexPath];
[[cell championName] setText:[item displayValue]];
[[cell championLogo] setImageWithURL:[NSURL URLWithString:[item imageUrl]]];
...
}
It increased the scrolling performance (still not perfectly smooth on iPhone 4 but it works well). Moreover it seems that no cell are disappearing anymore :-)
Thanks rdelmar!
I think somehow LBSelectorChampionViewController *item = [self itemAtIndexPath:indexPath]; from one of your cell's contentView was added to another cell's contentView, which would cause the previous cell's contentView to have 0 subview.
I think you are getting the first problem because of cell reusability. LBSelectorChampionViewController *item = [self itemAtIndexPath:indexPath]; will not always be added to the [cell contentView] as cell may not be nil. In addition, there might be a situation when this view will be added to another cell and removed from the previous. Anyway, this will be easier to fix if you subclass UITableViewCell and make LBSelectorChampionViewController part of this custom cell. This will make everything easier and you will never meet reusability problems.
I am not exactly familiar with SDWebImage, but maybe the problem is that it doesn't cache images and tries to load new image every time [championImage setImageWithURL:[NSURL URLWithString:[item imageUrl]]]; is hit. I've used AFNetworking in a few projects to load images and have never had any problems with it.
Hope this is helpful!
Cheers!
I added CustomImageView to the UITableViewCell.
UITableViewCell *cell = nil;
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell1"] autorelease];
}
CustomImageView *customIV = [[CustomImageView alloc] init];
[cell.contentView addSubView:customIV];
[customIV release];
But when I try to reload tableview, the error occurs.
error call stack is same as follows.
Output string is as follows.
-[CustomImageView superview]: message sent to deallocated instance 0x1f848f30
CustomImageView *customIV = [[CustomImageView alloc] initWithFrame:CGRectMake(x, y, w, h)];
[cell.contentView addSubView:customIV];
It's done with me when I've released memory.
So according to me, No need to release, because it'll deallocated memory.
Hopefully, it'll help you.Thanks.
Try to Comment this line[customIV release]; & run , it should not crash while reloading data.
The reason behind this is everytime it tries to create new custom view & releases it, so causes extra load on system & crash occurs.
You only want to add the image once to each cell. Change your code to this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell1"];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell1"] autorelease];
CustomImageView *customIV = [[CustomImageView alloc] init];
[cell.contentView addSubView:customIV];
[customIV release];
}
return cell;
}
If this doesn't work then you need to show your complete cellForRowAtIndexPath method. By only showing part of your code you make it difficult to help.
This error occur because each time CustomImageView object created when your cell created.
so, best way is first initialize object of CustomImageView then create your UITableView
such like,
CustomImageView *customIV put it in your .h file and then #synthesize it in .m File
(put this code above UITableView)
self.customIV = [[CustomImageView alloc] init];
Then create UITableView
self.tablView = [[UITableView alloc]init];
.
.
.
.
And in cellForRowAtIndexPath only add [cell.contentView addSubView:self.customIV];
i am using a custom uitablecellview like i used to do .But ,unfortunately ,this time an exception is thrown when a new cell appears.So,the same portion of code have worked perfectly many times before .The only difference is that the quality of images that i put into cells is very high.
Did you have ideas?or should i explain more?
Edit
some code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"CellView";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"SPViewControllCell" owner:self options:nil];
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell=cellView;
self.cellView=nil;
}
UILabel *itemTitle=(UILabel*)[cell viewWithTag:1];
[itemTitle setFont:[UIFont fontWithName:#"Renaissance" size:24]];
itemTitle.text=[titles objectAtIndex:indexPath.row];
UIImageView *img=(UIImageView*)[cell viewWithTag:2];
img.image=[images objectAtIndex:indexPath.row];
return cell;
}
The exception :
"'NSInvalidArgumentException', reason: '-[UITableViewCell objectAtIndex:]: unrecognized selector sent to instance 0x5f36be0'
"
Thank you.
I'm guessing but is titles an array created with something like:
-(void) viewDidLoad(){
// this will give you an autoreleased object
titles = [NSArray arrayWithObjects:#"Foo",...];
// titles will be released once the current event is handled
// try the alloc/init style method
titles = [[NSArray alloc] initWithObjects:... ];
// this will create an array with the retain count still at 1, thus won't be released
Same goes for images.
What you have is the classic memory management issue where a reference you think points to say an NSArray but in fact that array is long gone and happenes to have be replaced my a some random other object.
For completness:
- (void) viewDidUnload {
// clean up after your self
[titles release];
[images release];
}
From the exception description it seems that you are calling ** objectAtIndex:** method from a UITableViewCell class instance.
My question, in your code, what are the class type of your images and titles ivar?
I have a table cell to which I'm adding subviews programmatically. All the textual subviews work fine, but I can't get an image subview working at all.
You'll notice that I set the background color to black. This is simply to indicate to me that the subview is indeed being initialized and positioned properly within the cell. When I remove the background color there is nothing there.
Also, the cell style is UITableViewCellStyleDefault but I don't think that's pertinent for custom subviews. I want the image positioned on the right, which is why I'm not using the standard imageView property that cells offer.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
// ... add textual views ...
UIImageView *img = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"clock.png"]];
img.frame = CGRectMake(271.0f, 10.0f, 19.0f, 22.0f);
img.backgroundColor = [UIColor blackColor];
[cell addSubview:img];
}
// ... more code ...
return cell;
}
Your code looks fine to an extent. One issue I had when I was creating my own UITableViewCells in code was I was creating the cells with variables like you are but you shouldn't do this.
What you should do is create the cell and then set the variables. If cells are different you should use different reuse identifiers. Hope this makes sense if not let me know and I'll update.
I have a UITableView where I have the backgroud color set via
UIView *myView = [[UIView alloc] init];
if ((indexPath.row % 2) == 0)
myView.backgroundColor = [UIColor greenColor];
else
myView.backgroundColor = [UIColor whiteColor];
cell.backgroundView = myView;
[myView release];
The problem I find is that when I edit a table (via setEditing:YES...) some cells of the same color invariable are next to each other. How do I force UITableView to fully redraw. reloadData is not doing a great job.
Is there are deep-cleaning redraw?
I had this issue before so I'll share with you how I solved it:
You can use a boolean flag (say it's called needsRefresh) to control the behavior of cell creation in -cellForRowAtIndexPath:
An example:
- (UITableViewCell*) tableView:(UITableView *) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath {
UITableViewCell *cell = [tableView dequeueResuableCellWithIdentifier:SOME_ID];
if(!cell || needsRefresh) {
cell = [[[UITableViewCell alloc] init....] autorelease];
}
//.....
return cell;
}
So, when you need a hard reload, set the needsRefresh flag to YES. Simple as a pimple.
For me the accepted answer didn't really work since I had no idea when to set the needsRefresh back to YES.
What worked for me was:
- (UITableViewCell*) tableView:(UITableView *) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath {
UITableViewCell *cell = [tableView dequeueResuableCellWithIdentifier:customCellIdentifier];
if(nil == cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:customCellIdentifier];
}
//.....
return cell;
}
And then you change the customCellIdentifier value whenever you need to. This way the cells are also still reusable if you switch back to the original cell identifier.
The accepted method seems dirty, it just makes a bunch of new cells that are stored along with the bad ones. Here are a couple of solutions depending on your situation:
1.
first, for the situation described in the question you should not dump your cells and create new views on every cycle. You need to tag your view and then get it back when from the cell when you get a reuse cell:
- (UITableViewCell*) tableView:(UITableView *) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath {
UITableViewCell *cell = [tableView dequeueResuableCellWithIdentifier:SOME_ID];
if(!cell) {
cell = [[UITableViewCell alloc] init];
UIView *myView = [[UIView alloc] init];
cell.backgroundView = myView;
[myView setTag:5]; //<------
}
UIView *myView = [cell viewWithTag:5]; //<------
if ((indexPath.row % 2) == 0)
myView.backgroundColor = [UIColor greenColor];
else
myView.backgroundColor = [UIColor whiteColor];
return cell;
}
//then just reload the tableview.
2.
...or even better, why not just use the cell backgrouncolor and update that without creating a view.
3.
A sure way to really clear out old cached cells it to simply recreate the UITableView object.
4.
In most cases you dont need to destroy these cells, just keep track of your elements and update them after getting the reusable cell.You can tag all your elements, keep a array reference to them, find them thought the view hierarchy... Im sure theres a bunch of other ways.
5.
heres a one liner to directly purge all cells, although not best practice to mess with the internals of objects like this as they might change in future versions:
[(NSMutableDictionary*)[tableview valueForKey:#"_reusableTableCells" ] removeAllObjects];
I was able to solve this by adding a refresh variable to the table datasource. I used a dictionary for each cell, but there's an extra key called #"refresh":#"1", indicating the cell needs refreshing. Once it's updated, I set that key's value to #"0". So whenever the table is reloaded, make sure the key goes back to #"0" again.
#define TABLE_VIEW_CELL_DEFAULT_ID #"cellIdentifier"
#property (nonatomic, strong) NSString *tableViewCellIdentifier;
#property (nonatomic) NSUInteger tableViewCellIdentifierCount;
// By using a different cell identifier, this effectively flushes the cell
// cache because the old cells will no longer be used.
- (void) flushTableViewCellCache
{
self.tableViewCellIdentifierCount++;
self.tableViewCellIdentifier = [NSString stringWithFormat:#"%#%i", TABLE_VIEW_CELL_DEFAULT_ID, self.tableViewCellIdentifierCount];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.tableViewCellIdentifier];
if (cell == nil) {
cell = [[MyTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:self.tableViewCellIdentifier];
}
// rest of method...
}