iOS: CellForRowAtIndexPath cells are getting mixed up - ios

Going to start off by saying I've seen these questions:
iOS: UITableView mixes up data when scrolling too fast
(custom) UITableViewCell's mixing up after scrolling
Items mixed up after scrolling in UITableView
The first and last seemed very relevant to my problem, however I am fairly certain that I have logic for each section to determine what should appear in the cell (the data), and yet they still get mixed up.
The following is the relevant code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//Note: the if (cell == nil) thing is no longer required in iOS 6
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
if (closestRenter != nil)
{
NSLog(#"CLOSEST RENTER!");
[self setupCellsWithClosestRenterCell:cell atIndexPath:indexPath];
}
else
{
NSLog(#"NO CLOSEST RENTER");
[self setupCellsWithNoClosestRenterCell:cell atIndexPath:indexPath];
}
if (indexPath.section == 0)
{
for (UIView *view in cell.contentView.subviews)
{
NSLog(#"WHAT THE HECK");
[view removeFromSuperview];
}
}
return cell;
}
Relevant info here:
1) ClosestRenter is NOT nil... it exists. So the else clause should never be executed... and that is the case.
2) Within the code:
[self setupCellsWithClosestRenterCell:cell atIndexPath:indexPath];
There is a simple:
if (indexPath.section == 0)
{
cell.textLabel.text = #"PLACE HOLDER";
}
else
{
// Populate the cell with data. (creates a view (with controller etc) and loads it into the cell)
}
3) There are 2 sections at all times.
The problem is that section 0 (the first section) should have nothing more than that placeholder string. Section 1 should contain my custom subviews (in cells, which it does).
Section 0 initially has just the placeholder string, however once I scroll down (and the section is no longer visible) and scroll back up (quickly) it sometimes has a seemingly random cell from section 1 in there... what the heck? How? I'm reluctant to blame cell reuse but at this point outside of something really silly I don't know what it is.
The disturbing part here is that the cell in section 0 (there is only 1 row there) has no subviews. But when I scroll up and down fast it gets one (from section 1 apparently) and then I get the "WHAT THE HECK" log messages...
It should be worth mentioning that with the for loop (the one with the what the heck messages) does solve the problem (as it removes the unwanted subviews) but there has to be a better way. It feels wrong right now.
Any ideas?
(Feel free to mark this as a duplicate, but I'm fairly certain there is something else going on here).
Thanks.

So after a little bit of frustration, and careful analysis I found out why the cells were getting mixed up.
My assumption about cell reuse (particularly with the identifiers) was the problem.
Previously I was doing this:
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
Which is great and all, however there is a critical problem. All cells were technically the same underneath... after they are all allocated (not nil) the system could not determine which cell to reuse no matter what section it was.
This meant that any cell could be grabbed from the queue, with whatever it had in it, and stuck anywhere (despite my checks to make sure section 1 stuff went in section 1, and section 0 stuff (the fake renter) stayed in there).
The solution:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//Note: the if (cell == nil) thing is no longer required in iOS 6
static NSString *CellIdentifier1 = #"Cell";
static NSString *CellIdentifier2 = #"Cell2";
UITableViewCell *cell;
if (indexPath.section == 0)
{
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier1];
}
else
{
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier1];
}
}
else
{
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier1];
}
else
{
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier2];
}
}
if (closestRenter != nil)
{
NSLog(#"CLOSEST RENTER!");
[self setupCellsWithClosestRenterCell:cell atIndexPath:indexPath];
}
else
{
NSLog(#"NO CLOSEST RENTER");
[self setupCellsWithNoClosestRenterCell:cell atIndexPath:indexPath];
}
return cell;
}
As you can see, section 0 will get its own cell identifier. As will section 1. The result is that when a cell is to be dequeued, it will check which section the indexPath is currently at and grab the correct cell.
Ugh, such a frustrating problem but now it all makes sense :)

Adding an else solved my problem.
Where I reseted any changes that were made to the cell.
if (! self.cell) {
self.cell = [[LanguageCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
self.cell.accessoryType = UITableViewCellAccessoryNone;
}
else {
self.cell.checkImage.image = NO;
}

There are a couple of ways to deal with this cell reuse problem (and that is what the problem is), and the way you're doing it now is ok. The problem is that when you scroll down and back up again, the cell that's returned for section 0 could be a cell that was previously used for section 1, so it will have any subviews you put in there.
Another way to handle this, is to create two different prototype cells in the storyboard, and return the one with a simple label for section 0, and return the other one, with any subviews added in IB, for section 1. This way, you will always get the correct type of cell for each section without having to remove any subviews -- you only need to re-populate the cell with the correct data.

Related

tableview viewWithTag not retrieving UIElements objective-c

I am doing using some code that I have seen work before. Essentially a user answers yes or no on a post with some buttons. Pressing yes or no updates the database, which is working correctly, and it also updates the visible UI, which is not working. This UI updates the buttons so they one is selected, other is highlighted and both are disabled for user interaction. Also it makes changes to two UILabels. The method that these buttons calls needs to update the database and retrieve the buttons from the tableViewCell and update the changes I have the methods working in another ViewController so I can not understand the difference here. Here is my cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *simpleTableIdentifier = [NSString stringWithFormat:#"%ld,%ld",(long)indexPath.section,(long)indexPath.row];
NSLog(#" simple: %#",simpleTableIdentifier);
if (indexPath.row==0) {
ProfileFirstCell *cell = [self.tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
cell = [[ProfileFirstCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:simpleTableIdentifier];
}
cell = [self createProfileCell:cell];
return cell;
}else{
YesNoCell *cell =[self.tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell==nil) {
cell=[[YesNoCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
cell = [self createYesNoCell:cell:indexPath];
return cell;
}
}
Essentially what this does is create the users profile in the first cell, and load all the questions that user asks. This is the major difference I see between the old tableView and this tableView. In createYesNoCell I create the UIElements and create tags as follows
cell.yesVoteButton.tag=indexPath.row+ yesVoteButtonTag1;
cell.noVoteButton.tag=indexPath.row+ noVoteButtonTag1;
cell.yesCountLabel.tag=indexPath.row+ yesCountLabelTag1;
cell.noCountLabel.tag=indexPath.row+ noCountLabelTag1;
The buttons have the selector that initiates a number of things. It finds which button was pressed by the following.
NSInteger index;
if(sender.tag>=yesVoteButtonTag1){
NSLog(#"Yes button pressed");
votedYes=true;
index=sender.tag-yesVoteButtonTag1;
}else{
NSLog(#"No button Pressed");
votedYes=false;
index=sender.tag-noVoteButtonTag1;
}
UILabel *yesLabel = (UILabel*) [self.tableView viewWithTag:index+yesCountLabelTag1]; // you get your label reference here
UIButton *yesButton=(UIButton *)[self.tableView viewWithTag:index+1+yesVoteButtonTag1];
NSLog(#"Tag IN METHOD: %ld",index+yesVoteButtonTag1);
UILabel *noLabel = (UILabel*) [self.tableView viewWithTag:index+1+noCountLabelTag1]; // you get your label reference here
UIButton *noButton=(UIButton *)[self.tableView viewWithTag:index+noVoteButtonTag1];
These viewWithTag calls are nil when I look at them. The only difference that I can see from my earlier implementation is that the old one had sections and one row, while this one is all rows and one section. So replacing the indexPath.section with indexPath.row should account for that. Also I checked that the tag made in cellForRowAtIndexPath is the same as the row recovered in the yes/no vote method, because it is displaced by one because of the profile cell being created at indexPath.row==0. I tried passing the cell to the yes/no vote method and tried to recover the buttons and labels with contentView as some suggestions made on similar posts. However this didn't seem to solve my problem. Really would appreciate some insight on this.
have you call the '[tableView reload]' method to update the UITableView, it may helps.
Firstly, the table reuse identifier should be used for types of cells, not one for each cell. You have two types, so you should use two fixed reuse identifiers.
ProfileFirstCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"ProfileCell"];
if (cell == nil) {
cell = [[ProfileFirstCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"ProfileCell"];
}
and
YesNoCell *cell =[self.tableView dequeueReusableCellWithIdentifier:#"YesNoCell"];
if (cell==nil) {
cell=[[YesNoCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"YesNoCell"];
}
Secondly, rather than trying to get a reference to a cell after creating the table, which isn't working for you, you should initialize the cells completely when they are created. (TableView won't create cells unless they're visible, so you shouldn't rely on their existing at all.)
createProfileCell should really be called initializeProfileCell, because you're not creating the cell in it - you already did that in the line above, or recovered an old one.
Then your call to initializeProfileCell can take a flag specifying whether it is a Yes or No cell and set its properties accordingly.
cell = [self initializeProfileCell:cell isYes:(indexPath.section==0)];
Similarly with createYesNoCell --> initializeYesNoCell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"YOURCELL_IDENTIFIER";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
UILabel *title = (UILabel*) [cell viewWithTag:5];
UILabel *vensu =(UILabel*) [cell viewWithTag:7];
vensu.text = #"YOUR TEXT";
title.text = #"YOUR TEXT";
return cell;
}

how i get the right UITableViewCell objects textLable.text value,these reusable cells stored in a MutableArray?

I'm learning The development of iOS ,and I have a question .I can't debug it ,Google can’t give me answer.
Maybe I did not find a right way.
This question is when I create UITableViewCell objects and I reuse them ,and these cells are stored in a MutableArray, when I reuse cells from Buffer pool ,and get the value from the MutableArray, but the value of textLable.text is not right .
my code :
-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString * timeRing = #"ring";
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:timeRing];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:timeRing];
}else{
while ([cell.contentView.subviews lastObject]!=nil) {
[[cell.contentView.subviews lastObject] removeFromSuperview];
}
}
cell.accessoryType = UITableViewCellAccessoryNone;
if (self.cellArray.count<self.ringArray.count) {
self.timeRingItem = self.ringArray[indexPath.row];
cell.tintColor = [UIColor redColor];
cell.textLabel.text= self.timeRingItem.ringName;
[self.cellArray addObject:cell];
}
else{
cell = self.cellArray[indexPath.row];
}
return cell;
}
when the code run to cell = self.cellArray[indexPath.row]; ,the cell.textLable.text is wrong
so why? thanks for giving me answer.
The nature of a tableview means that cells will be re-used. When the user scrolls, it's going to take the top cell that just disappeared, and use it for the new cell. If you want to reference data, you need to keep the data in a separate array that's independent from the cells.

Customize a simple UITableViewCell efficiently

I would like to customize a simple UITableViewCell so that I run the customization only once and add values (e.g., cell title) later. My app's cell is more complex - it has subviews and uses auto layout; however, a simple example, I believe, will help in focusing on the objective.
I am using iOS 8, Xcode 6.X, Objective-C and Nibs (no storyboard) to keep it simple. I have not created a custom class for UITableViewCell. Instead, I have the following code:
- (void)viewDidLoad {
[super viewDidLoad];
//[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"Cell"];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1; //FIXED VALUE FOR EXAMPLE'S SAKE
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 3; //FIXED VALUE FOR EXAMPLE'S SAKE
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"tableView:cellForRowAtIndexPath:");
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
//UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
NSLog(#"cell == nil");
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
//CUSTOMIZING CELL THAT I WANT TO RUN ONLY ONCE
cell.backgroundColor = [UIColor redColor];
}
NSArray *numbersArray = #[#1,#2,#3];
cell.textLabel.text = [NSString stringWithFormat:#"%#", numbersArray[indexPath.row]];
return cell;
}
Which outputs:
tableView:cellForRowAtIndexPath:
cell == nil
tableView:cellForRowAtIndexPath:
cell == nil
tableView:cellForRowAtIndexPath:
cell == nil
FIRST QUESTION: Why is cell == nil run 3 times? It seems wasteful to run the customization code cell.backgroundColor = [UIColor redColor]; 3 times.
Now, when I enable:
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"Cell"];
And use:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
Instead of:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
I get the output:
tableView:cellForRowAtIndexPath:
tableView:cellForRowAtIndexPath:
tableView:cellForRowAtIndexPath:
SECOND QUESTION: Why isn't cell == nil run at all?
FINAL QUESTIONS: How can I make cell == nil run only once so that I format the UITableViewCell only once? Is there a better way to customize a simple cell, running the customization code only once?
Why is cell == nil run 3 times? It seems wasteful to run the customization code cell.backgroundColor = [UIColor redColor]; 3 times.
The table view most likely displays three cells at once, hence requiring three distinct cell objects.
Why isn't cell == nil run at all?
The documentation states that -dequeueReusableCellWithIdentifier:forIndexPath: always returns a valid cell if you registered the identifier previously. It basically takes care of checking if a new cell is required for you.
How can I make cell == nil run only once so that I format the UITableViewCell only once?
You don't. You will have to customize every single instance. I would recommend to use a custom subclass though, rather then messing with UITableViewCell from the outside.
The best way to do this, is to create a custom class for your cell, and do any customization that isn't dependent on the indexPath there. Usually, I do this in initWithCoder or awakeFromNib. You should register the nib in viewDidLoad; I don't see anything wrong with the code you mention in your comment to Christian's answer, unless the name of the file is wrong. It really isn't the view controller's business to be adding subviews or customizing your cell; that code belongs in the cell's class.
BTW, this doesn't keep the customization code from running multiple times. It needs to run once for each cell instance that you create, just like it does in your original code. The number of cells created will be equal to the number that fit on the screen at one time (plus one maybe).

UITableViewCell reused prematurely

I have a conditional breakpoint when the indexPath.section == 3 && indexPath.row == 3. The first time the breakpoint is triggered, and I run
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
The cell is the same cell as the cell at indexPath.section == 0 && indexPath.row == 0, rather than nil, which is what I would expect. I made sure the number of rows for section 3 is 4. Why isn't the cell nil?
I just give you suggestion, if you want to give restriction to reusability of cell then follow my below answer , But i would like to MENTION here that it is bad for memory management.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *CellIdentifier = [NSString stringWithFormat:#"S%1dR%1d",indexPath.section,indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
/// Put your code here.
}
/// Put your code here.
return cell;
}
UITableView does not necessarily start drawing with the first row. It could start drawing anywhere depending on which part of the view needs to be drawn at a given time.
The dequeueReusableCellWithIdentifier: method is only ever going to return nil once. After that it will forever return a cell that was previously used for some other row.
If you have two rows with incompatible cells, then you need to have a different identifier for each. Perhaps you should have an identifier for each section in your case for example.
The point is if you're drawing a thousand table view rows that look almost identical, except maybe the text is different, then you can create a single cell and then change the text but leave everything else (fonts, accessory view, etc) exactly the same as it goes through and draws each one.
Instead of [tableView dequeueReusableCellWithIdentifier:CellIdentifier];, use
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:CELL_IDENTIFIER];
...
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CELL_IDENTIFIER forIndexPath:indexPath];
with no checks for a nil cell.

DisclosureButton Indicator or Any kind of Accessory Type doesn't seem to appear when set on a UiTableViewCell

This is a weird thing I have been noticing lately. I am not able to see the accessory Type for my UITableView Cell even after setting the accessoryType with the following code
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"LocationCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier: CellIdentifier] autorelease];
}
if (indexPath.section == 0) {
cell.textLabel.text = #"Move to a New Location";
}else{
cell.textLabel.text = [self.locArray objectAtIndex:indexPath.row];
}
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
return cell;
}
It doesn't matter what accessory type I set. It always displays just the text label in the cell but, no button. Am I missing something? Note that this is not the first time I am using a UITableViewCell. I have never come across this problem earlier. Meanwhile I am testing this on iOS6 and have confirmed this behavior both on the iPad simulator as well as the device it self.
Well! I have solved this. My Viewcontroller was initially a subClass of UIViewController instead of UITableViewController. I did this thinking I would be adding extra views to the main View which would have nothing to do with the TableView. I then changed it to UITableViewController and voila it all worked perfect. But then this is a trade off between the flexibility provided by UiViewController and UitableViewController as I now have to add those extra views either in to the headerview or the footerview of the TableView. Nonetheless I have to deal with it any way. I hope this would help some one.

Resources