keyboardwarrior keyboardwarrior - 2 months ago 14
iOS Question

UITableView button duplicates while scrolling

I have created custom buttons for each viewcell(1 per cell). When scrolling the UITableView some viewcells have 2 buttons and that was not intended. I fail to see the error in my ways.

Please help!

Image: http://s18.postimg.org/v7djg84ah/buttons_App.png

Header:

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UITableViewDataSource>

- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView;

- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection: (NSInteger)section;

- (NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section;

- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
-(void)checkboxSelected:(id)sender;
@end


Implementation

#import "ViewController.h"

#define sectionCount 1
#define itemSection 0

@interface ViewController ()
{
NSArray *items;
}
@end

@implementation ViewController

- (void)viewDidLoad
{
[super viewDidLoad];

items = @[@"itemdd", @"item2",@"itemdd", @"item2",@"itemdd", @"item2",@"itemdd", @"item2",@"itemdd", @"item2",@"itemdd", @"item2",@"itemdd", @"item2",@"itemdd", @"item2",@"itemdd", @"item2",@"itemdd", @"item2"];

}

- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}

-(NSInteger) numberOfSectionsInTableView:(UITableView *)tableView
{
return sectionCount;
}

- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
switch(section)
{
case itemSection:
{
return [items count];
}

default:
return 0;

}
}

-(NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
switch (section)
{
case itemSection:
return @"Items";
default:
return @"woot";
}
}


-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell =[tableView dequeueReusableCellWithIdentifier:@"itemCell"];

switch(indexPath.section)
{
case itemSection:
cell.textLabel.text = items[indexPath.row];
break;
default:
cell.textLabel.text=@"Unknown";
}
NSInteger x,y;

x =cell.frame.origin.x +100; y = cell.frame.origin.y + 10;
UIButton *checkbox;

checkbox = [[UIButton alloc] initWithFrame:(CGRectMake(x,y,20,20))];

[checkbox setBackgroundImage:[UIImage imageNamed:@"notSelectedButton.png"]forState:UIControlStateNormal];

[checkbox setBackgroundImage:[UIImage imageNamed:@"checkedButton.png"]forState:UIControlStateSelected];
[checkbox setBackgroundImage:[UIImage imageNamed:@"uncheckedButton.png"]forState:UIControlStateHighlighted];

checkbox.adjustsImageWhenHighlighted = YES;
[checkbox addTarget:self action:@selector(checkboxSelected:) forControlEvents:UIControlEventTouchDown];
[cell.contentView addSubview:checkbox];

return cell;
}

-(void)checkboxSelected:(id)sender
{
bool selected = [(UIButton *)sender isSelected];
if (selected)
{
[(UIButton *)sender setSelected:false];
}
else
{
[(UIButton *)sender setSelected:true];
}


}
@end

Answer

First, look at this statement in your code:

UITableViewCell *cell =[tableView dequeueReusableCellWithIdentifier:@"itemCell"];

The method -dequeueReusableCellWithIdentifier will either pull an already created table view cell, or generate a new one if no reusable cell is available.

Now review this statement:

checkbox = [[UIButton alloc] initWithFrame:(CGRectMake(x,y,20,20))];
…
[cell.contentView addSubview:checkbox];

If your cell was just initialized, this code works fine. However, when you scroll your table, your code will start pulling old table view cells that were thrown into the reusable queue. Those old table view cells already contain a UIButton. So every time you call -addSubview: on an old cell, you add duplicate buttons.

There are many ways to fix this. Here's one solution for you:

static NSInteger const checkboxTag = 123;
checkbox = (UIButton *)[cell.contentView viewWithTag:checkboxTag];
if (!checkbox) {
    checkbox = [[UIButton alloc] initWithFrame:(CGRectMake(x,y,20,20))];
    checkbox.tag = checkboxTag;

    [checkbox setBackgroundImage:[UIImage imageNamed:@"notSelectedButton.png"]forState:UIControlStateNormal];
    [checkbox setBackgroundImage:[UIImage imageNamed:@"checkedButton.png"]forState:UIControlStateSelected];
    [checkbox setBackgroundImage:[UIImage imageNamed:@"uncheckedButton.png"]forState:UIControlStateHighlighted];
    checkbox.adjustsImageWhenHighlighted = YES;
    [checkbox addTarget:self action:@selector(checkboxSelected:) forControlEvents:UIControlEventTouchDown];
    [cell.contentView addSubview:checkbox];
}

EDIT:

Something else worth pointing out. This statement:

x =cell.frame.origin.x +100; y = cell.frame.origin.y + 10;

By referencing cell.frame, you're creating a UIButton relative to the cell's position in the table view, not the cell itself. Set x = 100; y = 10; to make the offset relative to the the cell's bounds instead.

Comments