How to use Reusable Cells in uitableview for IOS

Ok. I cant seem to get a firm understanding on how tableviews work. Would someone please explain to me how cells are reused in tableviews especially when scrolling? One of the major pain points I have about this is the fact that when I create an action in one cell, other cells are affected when I scroll. I tried using an array as the backend for the model but still I get cells that change when not suppose to. The hard thing to figure out is why do they change when the the model in the array is not changed.

A simple example:

  • Caching on AFNetworking 2.0
  • how to block user , if he/she not connected to internet connection when my app load
  • Multiline label in UIStackView
  • iOS - color on xcode simulator is different from the color on device
  • How to format certain words of a cell's text label
  • Resize images automatically in CollectionViewCell in CollectionView with Auto Layout
  • table view cells with the button “like”. When I click the button in one of the cells, the button text changes to “Unlike”(So far so good). But When I scroll down, other cells also show “Unlike” even though I haven’t selected them. And when I scroll up, the cells I originally selected change again and newer ones are changed as well.

    I cant seem to figure this out. If you can show me a working example source code, that would be awesome!!! Thanks!

    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
    
        likeState = [[NSMutableArray alloc]init];
    
        int i =0;
        for (i=0; i<20; i++) {
            [likeState addObject:[NSNumber numberWithInt:0]];
        }
    } 
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
    
        UIButton *myButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
        [myButton setTitle:@"Like" forState:UIControlStateNormal];
        [myButton addTarget:self action:@selector(tapped:) forControlEvents:UIControlEventTouchUpInside];
        myButton.frame = CGRectMake(14.0, 10.0, 125.0, 25.0);
        myButton.tag =indexPath.row;
        [cell.contentView addSubview:myButton];
    
        if (cell ==nil) {
    
    
        }
    
        if ([[likeState objectAtIndex:indexPath.row]boolValue]==NO) {
            [myButton setTitle:@"Like" forState:UIControlStateNormal];
    
        }
        else{
            [myButton setTitle:@"Unlike" forState:UIControlStateNormal];
    
    
        }
    
    
        return cell;
    }
    -(void)tapped:(UIButton *)sender{
    
    [likeState replaceObjectAtIndex:sender.tag withObject:[NSNumber numberWithInt:1]];
    
        [sender setTitle:@"Unlike" forState:UIControlStateNormal];
    
    }
    

    6 Solutions Collect From Internet About “How to use Reusable Cells in uitableview for IOS”

    I am assuming you are doing this via Storyboard and since you haven’t created your button via the Interface Builder, you need to check if the cell that is being re-used already has the button or not.
    As per your current logic, you are creating a new button instance ever time the cell reappears.

    I’d suggest the following:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        static NSString *CellIdentifier = @"Cell";
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
    
        //following is required when using XIB but not needed when using Storyboard
        /*
         if (cell == nil) {
             cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
         }
         */
        //Reason:
        //[1] When using XIB, dequeueReusableCellWithIdentifier does NOT create a cell so (cell == nil) condition occurs
        //[2] When using Storyboard, dequeueReusableCellWithIdentifier DOES create a cell and so (cell == nil) condition never occurs
    
        //check if cell is being reused by checking if the button already exists in it
        UIButton *myButton = (UIButton *)[cell.contentView viewWithTag:100];
    
        if (myButton == nil) {
            myButton = [UIButton buttonWithType:UIButtonTypeCustom];
            [myButton setFrame:CGRectMake(14.0,10.0,125.0,25.0)];
            [myButton setTag:100]; //the tag is what helps in the first step
            [myButton setTitle:@"Like" forState:UIControlStateNormal];
            [myButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
            [myButton addTarget:self action:@selector(tapped:andEvent:) forControlEvents:UIControlEventTouchUpInside];
            [cell.contentView addSubview:myButton];
    
            NSLog(@"Button created");
        }
        else {
            NSLog(@"Button already created");
        }
    
        if ([likeState[indexPath.row] boolValue]) {
            [myButton setTitle:@"Unlike" forState:UIControlStateNormal];
        }
        else {
            [myButton setTitle:@"Like" forState:UIControlStateNormal];
        }
    
        return cell;
    }
    

    -(void)tapped:(UIButton *)sender andEvent:(UIEvent *)event
    {
        //get index
        NSSet *touches = [event allTouches];
        UITouch *touch = [touches anyObject];
        CGPoint currentTouchPosition = [touch locationInView:self.tableView];
        NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:currentTouchPosition];
    
        //toggle "like" status
        if ([likeState[indexPath.row] boolValue]) {
            [likeState replaceObjectAtIndex:indexPath.row withObject:@(0)];
            [sender setTitle:@"Like" forState:UIControlStateNormal];
        }
        else {
            [likeState replaceObjectAtIndex:indexPath.row withObject:@(1)];
            [sender setTitle:@"Unlike" forState:UIControlStateNormal];
        }
    }
    

    The biggest problem is that you create button each time you update cell.

    for example if you have visible 4 roes on the screen like this :

    *-----------------------*
    | cell A   with button  |
    *-----------------------*
    | cell B   with button  |
    *-----------------------*
    | cell C   with button  |
    *-----------------------*
    | cell D   with button  |
    *-----------------------*
    

    now when you scroll down so the cell A is not visible any more it get reused and placed underneeth :

    *-----------------------*
    | cell B   with button  |
    *-----------------------*
    | cell C   with button  |
    *-----------------------*
    | cell D   with button  |
    *-----------------------*
    | cell A   with button  |
    *-----------------------*
    

    but for cell A it gets called cellForRowAtIndexPath again.
    What you did is placing another button on it. So you actually have:

    *-----------------------*
    | cell B   with button  |
    *-----------------------*
    | cell C   with button  |
    *-----------------------*
    | cell D   with button  |
    *-----------------------*
    | cell A with 2 buttons |
    *-----------------------*
    

    You can see how you can quite soon have a lot of buttons piling up. You can fix this by storyboard as @Timur Kuchkarov suggested, or you fix your code by

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
    
    
    
        if (cell ==nil) {
             UIButton *myButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
            [myButton setTitle:@"Like" forState:UIControlStateNormal];
            [myButton addTarget:self action:@selector(tapped:) forControlEvents:UIControlEventTouchUpInside];
            myButton.frame = CGRectMake(14.0, 10.0, 125.0, 25.0);
            myButton.tag = 10;
            [cell.contentView addSubview:myButton];
    
        }
    
        UIButton * myButton = (UIButton * )[cell.contentView viewWithTag:10]
    
    
        if ([[likeState objectAtIndex:indexPath.row]boolValue]==NO) {
            [myButton setTitle:@"Like" forState:UIControlStateNormal];
    
        }
        else{
            [myButton setTitle:@"Unlike" forState:UIControlStateNormal];
        }
    
    
        return cell;
    }
    

    In this way you add just 1 button, if cell was not reused (so it has nothing on it).

    It this way you can not rely on mutton tag number for function tapped, (I wouldn’t anyway), so you have to change it.

    This part is not tested :

    You can check parent of the button to see witch cell it belongs.

    UITableViewCell * cell = [[button superview] superview] /// superview of button is cell.contentView
    NSIndexPath * indexPath = [yourTable indexPathForCell:cell];
    

    To resuse the cell , put your cell identifier value in interface builder .

    First of all you are always setting @(1)([NSNumber numberWithInt:1], better to use @(YES) or @(NO) for this) to your array when you tap cell button. That’s why you’ll see incorrect results. Then you are always adding button to your cells, so if it’s reused 10 times, you’ll add 10 buttons there. It’s better to use xib or storyboard prototype for that.

    I need to see your code, but i can say u didn’t change your model or you changed it all of rows in your array.

    You can use some class extended from UITableViewCell and in “cellForRowAtIndexPath” method use it as reusable cell.

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
     {
       MyCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyCell"];
    
    
        // Configure the cell...
        MyModel *model = [_models objectAtIndex:indexPath.row];
        cell.nameLabel.text = model.name;
        cell.image.image = model.image;
        cell.likeButton.text = model.likeButtonText;
        return cell;
     } 
    

    and then in your “didSelectRowAtIndexPath” method change “model.likeButtonText”.

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
       MyCell cell* =     [tableView cellForRowAtIndexPath:indexPath];
       MyModel *model = [_models objectAtIndex:indexPath.row];
       model.likeButtonText = [model.likeButtonText isEqual:@"like"]?@"unlike":@like;
       cell.likeButton.text = model.likeButtonText;
    }
    

    This will update your cell

    Swift 2.1 version of accepted answer. Works great for me.

            override func viewDidLoad() {
            super.viewDidLoad();
    
            self.arrayProducts = NSMutableArray(array: ["this", "that", "How", "What", "Where", "Whatnot"]); //array from api for example
            var i:Int = 0;
            for (i = 0; i<=self.arrayProducts.count; i++){
                let numb:NSNumber = NSNumber(bool: false);
                self.arrayFavState.addObject(numb); //array of bool values
                }
            }
    

    TablView Datasource methods ::

            func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCellWithIdentifier("SubCategoryCellId", forIndexPath: indexPath) as! SubCategoryCell;
    
    
            cell.btnFavourite.addTarget(self, action: "btnFavouriteAction:", forControlEvents: UIControlEvents.TouchUpInside);
    
            var isFavourite: Bool!;
            isFavourite = self.arrayFavState[indexPath.row].boolValue;
            if isFavourite == true {
                cell.btnFavourite.setBackgroundImage(UIImage(named: "like-fill"), forState: UIControlState.Normal);
            } else {
                cell.btnFavourite.setBackgroundImage(UIImage(named: "like"), forState: UIControlState.Normal);
            }
    
    
            return cell;
        }
    
        func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return self.arrayProducts.count;
        }
    

    Button Action method ::

            func btnFavouriteAction(sender: UIButton!){
    
            let position: CGPoint = sender.convertPoint(CGPointZero, toView: self.tableProducts)
            let indexPath = self.tableProducts.indexPathForRowAtPoint(position)
            let cell: SubCategoryCell = self.tableProducts.cellForRowAtIndexPath(indexPath!) as! SubCategoryCell
            //print(indexPath?.row)
            //print("Favorite button tapped")
            if !sender.selected {
                cell.btnFavourite.setBackgroundImage(UIImage(named: "like-fill"), forState: UIControlState.Normal);
                sender.selected = true;
                self.arrayFavState.replaceObjectAtIndex((indexPath?.row)!, withObject:1);
            } else {
                cell.btnFavourite.setBackgroundImage(UIImage(named: "like"), forState: UIControlState.Normal);
                sender.selected = false;
                self.arrayFavState.replaceObjectAtIndex((indexPath?.row)!, withObject:0);
            }
    
        }