Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UITableViewCell selector setSelected:animated: gets called many times?

I've discovered a strange behavior with setSelected:animated: in my custom UITableViewCell class. I discovered that this function gets called multiple times if I click on a cell in my table. I am wondering if this is normal behavior or a bug in my code.

To help with debugging, I've modified the setSelected:animated: function in my custom UITableViewCell class implementation as such:

- (void)setSelected:(BOOL)selected animated:(BOOL)animated {

[super setSelected:selected animated:animated];

// Configure the view for the selected state.
if (selected)
    NSLog(@"Yes %X", &self);
else
    NSLog(@"No %X", &self);

}

If I click on a cell in the simulator, here is what I get in the console:

2011-03-22 22:05:26.963 marketPulse[3294:207] Yes BFFFDDD0
2011-03-22 22:05:26.964 marketPulse[3294:207] Yes BFFFDE30

You would think that I would get only 1 entry, since I only clicked on 1 cell.

And if I click on a different cell after that:

2011-03-22 22:07:11.014 marketPulse[3294:207] No BFFFD890
2011-03-22 22:07:11.016 marketPulse[3294:207] No BFFFDD00
2011-03-22 22:07:11.017 marketPulse[3294:207] Yes BFFFDDD0
2011-03-22 22:07:11.017 marketPulse[3294:207] Yes BFFFDE30

If I click on the same cell 2 times in a row, I get more than 2 Yes:

2011-03-22 22:08:41.067 marketPulse[3294:207] Yes BFFFDDD0
2011-03-22 22:08:41.068 marketPulse[3294:207] Yes BFFFDE30
2011-03-22 22:08:41.069 marketPulse[3294:207] Yes BFFFDE30

The more times I click the same cell, the more Yes I will get, and if I click on a different cell after that, I'll get a lot of No

I put a breakpoint before the NSLog, and looking at the debugger, it seems that all the repeated calls are coming from the same object.

Here is a part of my tableView:cellForRowAtIndexPath: function so you can see how my cells are being treated:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {


static NSString *ContentCellIdentifier = @"newsTableCellContent";

UITableViewCell *cell;


//index of cell data in tableData
NSUInteger index = indexPath.row / 2;

...

//content of story
else if( [indexPath row] % 2 == 1 ) {

    cell = [tableView dequeueReusableCellWithIdentifier:ContentCellIdentifier];

    if (cell == nil) {
        NSArray *topLevelObjects = [[NSBundle mainBundle]
                                    loadNibNamed:@"newsTableCells"
                                    owner:nil options:nil];

        for (id currentObject in topLevelObjects) {
            if ( [currentObject isKindOfClass:[newsTableCellContent class]] ) {
                cell = currentObject;
                break;
            }
        }
    }

    ((newsTableCellContent *)cell).content.text = [[tableData objectAtIndex:index] description];

}   

return cell;
}

Everything works fine so its hard to tell if the repeat calls to setSelected:animated: are intentional or not. If this is normal operation, I can make do with another method, but I would just like to know if this is suppose to happen or not.

Thanks

like image 637
spybart Avatar asked Mar 23 '11 05:03

spybart


3 Answers

What's going on is simply that the UITableView keeps track of which cells are selected in the table.

Since cells are reused when you scroll through a large table view, the table view has to keep the list of selected cells separate. Not only that, but whenever it reuses a cell it has to set its selected property, because it may be using an old, invalid selected state from a previous incarnation.

When you tap a cell, several things happen: the previously selected cell is deselected (using setSelected:). The new cell is highlighted. It's de-highlighted (at least if you tap, instead of holding your finger down), and the setSelected: method is called because the new cell was selected. That's one.

The second call is a delayed perform call, possibly from a point where the table view didn't yet know what the final state of the table would be. This call goes to _selectAllSelectedRows, which, as the name suggests, calls 'setSelected:animated:' on all selected rows. That's the second call. The reason for this is most likely to address potential issues due to the the table view being in a "transition", but who knows.

Whether it's a bug or not is up for interpretation. A fix for the duplicate calls is to simply do:

if (self.selected == selected) return;

right before the call to super (you do not have to call super if self.selected == selected).

like image 196
Kalle Avatar answered Nov 14 '22 17:11

Kalle


This is a normal behavior if you're using iPad. (it is only called once on iPhone).

In order to stop getting multiple "setSelected:YES" or multiple "setSelected:NO", all you have to do is this:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

Now, 1 click on any cell gives you:

  • 1 entry of setSelected:YES animated:NO
  • 1 entry of tableView: didSelectRowAtIndexPath:
  • 1 entry of setSelected:NO animated:YES

So, calls are now stable regardless of what you do.

like image 27
OlDor Avatar answered Nov 14 '22 16:11

OlDor


Ideally you should not be calling setSelected from anywhere in your code. UIKit will take care of calling it.

If you want to show a cell/row as selected in cellForRowAtIndexPath method simply call

tableView.selectRowAtIndexPath(indexPath, animated: true, scrollPosition: .None)

for that specific indexPath.

Again never ever call setSelected explicitly unless you really mean to.

like image 4
LazyLastBencher Avatar answered Nov 14 '22 17:11

LazyLastBencher