Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

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];

}
like image 241
user3926564 Avatar asked Aug 12 '14 06:08

user3926564


People also ask

What are the cells of UITableView?

The cells of UITableView are instances of UITableViewCell or its subclasses. It is the table view that adds, removes, and arranges cells in its view hierarchy. Table views reuse cells that go out of the screen to display new elements, so that:

What are reusable cells in a table view?

Reusable cells are a fundamental piece of a working table view. Not only you need to understand how cells are reused by a table view, but you also need to design properly so that the table view will resize them automatically using Auto Layout. A table view displays its elements using specialized subviews called cells.

How to reload data from uitableviewcontroller?

Were you using a UITableViewController? One of the few things it does is call reloadData () on the table view. Now you have to do it yourself. Place it after you assign the data source to the table view.

Why use a UITableView instead of a Tableview?

Table views are more versatile than you might think. For example, many developers make their life harder using a scroll view when a UITableView would be a better choice. Finally, architecture is crucial for table views.


2 Answers

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];
    }
}
like image 124
staticVoidMan Avatar answered Nov 13 '22 20:11

staticVoidMan


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];
like image 24
Marko Zadravec Avatar answered Nov 13 '22 21:11

Marko Zadravec