3

I have an iPhone app problem that's been bugging me for a few days and it really doesn't seem like it should be this difficult so I'm sure I'm missing something obvious. I have researched plenty of forum discussions on "similar" topics but nothing that actually addresses this issue, specifically.

To be clear, if there is some piece of documentation or some other source that I should research, please point me in the right direction.

Here goes...

I have a list of items that I display to the user within a table (uitableview). The cell (uitableviewcell) for each item is custom and contains an image and a Like button (uibutton). As expected, for each item in the the table, the user can click the Like button or ignore it. The like button calls a separate process to update the server. Simple, right?

So, here is the issue:

When the Like button is clicked on a particular cell, the Selected state works fine but the moment you scroll the cell out of view, other random cells in the table show the Like button as Selected even though they were never touched. Because the cells are reused, for obvious performance reasons, I understand why this could happen. What I don't understand is why my approach (see code below) would not override or reset the button's state the way I think it should. For brevity, I am only including the relevant code here (hopefully formatted properly):

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"MyCustomCell";
    MyCustomViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if (cell == nil) {
    cell = [[MyCustomViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
}

    NSString *myRating = [[self.dataArray objectAtIndex:indexPath.row] valueForKey:@"my_rating"];

    // Create the Like button
    UIButton *likeButton = [[UIButton alloc] initWithFrame:CGRectMake(260, 68, 40, 40)];
    [likeButton setImage:[UIImage imageNamed:@"thumbsUp"] forState:UIControlStateNormal];
    [likeButton setImage:[UIImage imageNamed:@"thumbsUpSelected"] forState:UIControlStateSelected];
    if (myRating == @"9") {
        [likeButton setSelected:YES];
    }
    [likeButton setTitle:@"9" forState:UIControlStateNormal];
    [likeButton setTag:indexPath.row];
    [cell.contentView addSubview:likeButton];
    [likeButton addTarget:self action:@selector(likeButtonPressed:) forControlEvents:UIControlEventTouchUpInside];

    return cell;
}

- (void)likeButtonPressed:(UIButton *)sender {

    // Changed the Selected state on the button
    UIButton *button = (UIButton *)sender;
    button.selected = !button.selected;

    // Create a new object with the user's rating and then replace it in the dataArray
    NSString *ratingText = sender.titleLabel.text;    
    NSMutableArray *myMutableArray = [[self.dataArray objectAtIndex:row] mutableCopy];
    [myMutableArray setValue:ratingText forKey:@"my_rating"];
    [self.dataArray replaceObjectAtIndex:row withObject:myMutableArray];
}

So, I've been through many iterations of this but I can't seem to get the state of the button to show the Selected image for those items that are Liked and keep the normal image for those items that have not been Liked.

Any help or advice would be greatly appreciated.

acabrera
  • 33
  • 1
  • 3
  • I have the same issue. I have a radioButton in HeaderSection of tableView. When I select a button in headerSection, random radioButtons get selected in the tableView. Did you solve your issue. Can you please help me.? I have to select single radioButton in tableView. – Bella Mar 21 '18 at 06:36

3 Answers3

9

there is a simple way out of this. you can trick the button to keep being in the latest selected state.

make a mutable array, for the purpose of keeping the selected state of the button

selectedButton = [[NSMutableArray alloc]init];//i've already defined the array at the .h file

for (int i = 0; i<yourTableSize; i++) //yourTableSize = how many rows u got
{
    [selectedButton addObject:@"NO"];
}

at the tableviewcell method, declare your button and set it so that it refers to the mutablearray to set it's selected state

static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil) 
{ 
    cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
}

UIImage *img = [UIImage imageNamed:@"btn_unselected.png"];
UIButton *toggleButton = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, img.size.width, img.size.height)];
[toggleButton setImage:img forState:UIControlStateNormal];
img = [UIImage imageNamed:@"btn_selected.png"];
[toggleButton setImage:img forState:UIControlStateSelected];
[toggleButton setTag:indexPath.row+100];//set the tag whichever way you wanted it, i set it this way so that the button will have tags that is corresponding with the table's indexpath.row
[toggleButton addTarget:self action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:toggleButton];

//and now we set the button's selected state, everytime the table reuse/redraw the cell the button will set it's selected state according to the array

if([[selectedButton objectAtIndex:indexPath.row]isEqualToString:@"NO"])
{
    [toggleButton setSelected:NO];
}
else 
{
    [toggleButton setSelected:YES];
}

return cell;

and finally, create the method which the button triggered when the button is pressed, to change it's selected state

-(void)buttonPressed:(UIButton*)sender
{
    int x = sender.tag - 100; //get the table's row
    if([sender isSelected]) //if the button is selected, deselect it, and then replace the "YES" in the array with "NO"
    {
        [selectedButton replaceObjectAtIndex:x withObject:@"NO"];
        [sender setSelected:NO];
    }
    else if (![sender isSelected]) //if the button is unselected, select it, and then replace the "NO" in the array with "YES"
    {
        [selectedButton replaceObjectAtIndex:x withObject:@"YES"];
        [sender setSelected:YES];
    }
}
Yonathan Jm
  • 442
  • 3
  • 12
4

The problem is that every time you create or reuse a cell you're giving it a new like button, so when you reuse a cell where the like button has been activated, you're giving it a deactivated like button but the old, activated like button is still there as well.

Instead of creating a like button every time you need a cell, you should just be setting the state of an existing like button. See the answers to this question for some possible ways of handling that.

Community
  • 1
  • 1
yuji
  • 16,695
  • 4
  • 63
  • 64
  • This would only work if there is only one cell being displayed at a time. A better option would be to add the button to the custom cell itself instead of inserting one each time. – brendan Feb 23 '12 at 23:54
  • 1
    Um, no. I never said one like button for the whole table, but one like button per cell. Notice that in the OP's code, every time a cell gets recycled and reused, it gets another button added to it. A cell that has been reused five times will have five buttons on top of each other. – yuji Feb 24 '12 at 00:01
  • Thanks, yuji. It makes perfect sense now that I should not have been continually allocating a new button. Just to see it for myself, I randomized the location of the button within the cell. As I scrolled up and down, the buttons were popping up all over the place. So, based on the other answer you pointed me to, I need to expand my current uitableview subclass (MyCustomViewCell) to contain the buttons and labels that I need and simply make use of them within cellForRowAtIndexPath. I'm not exactly sure of the best way to accomplish that but I will figure it out. Thanks for the advice. – acabrera Feb 24 '12 at 20:35
1

This is not valid (At least not if you are trying to compare strings my contents instead of addresses):

if (myRating == @"9")

Try this:

if ([myRating isEqualToString:@"9"])

And +1 to yuji for noticing the multiple button creation.

NJones
  • 27,139
  • 8
  • 70
  • 88