Birrel
Birrel

Reputation: 4834

iOS - sub views of UITableviewCell do not reload/update

I've got a table view where the cells are created as follows:

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

    cell.textLabel.text = @"TempTitle";

    cell.textLabel.font = [UIFont fontWithName: @"AppleSDGothicNeo-Bold" size:16.0];

    [cell setSelectionStyle:UITableViewCellSelectionStyleNone];

    UILabel *numberLabel = [[UILabel alloc] init];
    [numberLabel setFrame:CGRectMake(230.0, 5.0, 40.0, 30.0)];
    [numberLabel setText:[NSString stringWithFormat:@"%@", [self.arrayOne objectAtIndex:indexPath.row]]];

    UIButton *buttonDown = [[UIButton alloc] init];
    [buttonDown setFrame:CGRectMake(190.0, 5.0, 40.0, 30.0)];
    buttonDown.tag = indexPath.row;
    [buttonDown addTarget:self action:@selector(quantityDown:) forControlEvents:UIControlEventTouchUpInside];

    UIButton *buttonUp = [[UIButton alloc] init];
    [buttonUp setFrame:CGRectMake(270.0, 5.0, 40.0, 30.0)];

    [cell addSubview:buttonDown];
    [cell addSubview:numberLabel];
    [cell addSubview:buttonUp];

    return cell;
}

where self.arrayOne (name changed for this thread) holds integer values that are displayed in each cell. The method when buttonDown is selected is as following:

- (void) quantityDown:(id)sender {
    int clicked = ((UIButton *)sender).tag;

    ... math to lower int by one and save the new value in arrayOne
    ... this part works just fine. The value does go down, as intended.

    [self.tableView reloadData];
}

When the tableView reloads, a new label is printed on top of the existing label in that cell, as can be seen in the image below:

Screen shot

I can only assume that new buttons are being printed on top of the existing ones as well. This is both expensive and makes the numbers unreadable (especially if you change it multiple times!). Leaving the view and coming back to it shows the new numbers cleanly, but only until you start changing them again.

A similar effect happens when I use the UISegmentedControl at the top. Selecting one or the other changes the contents of the table and runs [self.tableView reloadData]. The textLabel for each cell reloads just fine when this method is called, but the sub views do not reload, and instead stack upon one another.

How would I go about writing this so that there is only ever one subview in each cell, instead of multiple stacked upon one another? Something speedy and not expensive on resources. Maybe removing all subviews and then adding the new ones? I tried something like

[[cell.contentView viewWithTag:tagVariable]removeFromSuperview];

to no avail. Really, I only need to modify the one cell in the table that the user is clicking in. Except for when the user uses the UISegmentedControl at the top, then all the cells need to be modified.

Thanks in advance.

EDIT 1:

Created a custom UITableViewCell class...

@interface SSCustomTableViewCell : UITableViewCell

@property (nonatomic) UILabel *quantity;
@property (nonatomic) UIButton *down;
@property (nonatomic) UIButton *up;

@end

@implementation SSWeightsTableViewCell

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        self.quantity = [UILabel new];
        [self.contentView addSubview:self.quantity];
        self.down = [UIButton new];
        [self.contentView addSubview:self.down];
        self.up = [UIButton new];
        [self.contentView addSubview:self.up];
    }
    return self;
}

- (void) layoutSubviews {
    [super layoutSubviews];

    [self.down setFrame:CGRectMake(190.0, 5.0, 40.0, 30.0)];
    [self.down setBackgroundColor:[UIColor purpleColor]];

    [self.up setFrame:CGRectMake(270.0, 5.0, 40.0, 30.0)];
    [self.up setBackgroundColor:[UIColor purpleColor]];

    [self.quantity setFrame:CGRectMake(230.0, 5.0, 40.0, 30.0)];
    [self.quantity setTintColor:[UIColor purpleColor]];
}

@end

and then in my UITableView class...

#import "SSCustomTableViewCell.h"

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self.tableView
     registerClass:[SSCustomTableViewCell class]
     forCellReuseIdentifier:NSStringFromClass([SSCustomTableViewCell class])
     ];
}

and

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

    UITableViewCell *cell = [tableView 
                             dequeueReusableCellWithIdentifier: NSStringFromClass([SSWeightsTableViewCell class])
                             forIndexPath:indexPath
                             ];
}

but now all I see is the following...

new screen show

where the subviews are not correctly laid out, nor are they the correct size. Also, within the cellForRowAtIndexPath method, I cannot access the cell's components. When I enter

cell.quantity

I get an error saying that "Property 'quantity' not found on object of type UITableViewCell*"

So I tried

SSCustomTableViewCell *cell = [tableView 
                             dequeueReusableCellWithIdentifier: NSStringFromClass([SSWeightsTableViewCell class])
                             forIndexPath:indexPath
                             ];

and the error goes away, but it still looks the same when I run it.

Edit 2: Working Solution

All I had to do was add

cell = [[UITableViewCell alloc]init];

within the cellForRowAtIndexPath method. Everything else stayed the same, and no custom classes required.

Upvotes: 1

Views: 5710

Answers (2)

Charan Giri
Charan Giri

Reputation: 1097

Try this in cell configure method, this will make sure objects doesn't overlap

for (UIView *subview in [cell.contentView subviews]) {
    [subview removeFromSuperview];
}

example:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellId = @"cellId";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (cell==nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];

}

for (UIView *subview in [cell.contentView subviews]) {
    [subview removeFromSuperview];
}

return cell;
}

Hope this will help

Upvotes: 0

drewag
drewag

Reputation: 94723

You are seeing this error because cell instances are reused. If you add a view each time you configure the cell, you will be adding to cells that already have the subviews on them. You should create your own subclass of UITableViewCell and add the subviews to it in init. Then your method above would just set the values on the various subviews.

Your cell would look something like this:

@interface MyCustomCell : UITableViewCell

@property (nonatomic, strong) UILabel *myCustomLabel;

@end

@implementation MyCustomCell

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        self.myCustomLabel = [UILabel new];
        [self.contentView addSubview:self.myCustomLabel];
    }
    return self;
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    // Arrange self.myCustomLabel how you want
}

@end

Then your view controller would look like this:

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self.tableView
        registerClass:[MyCustomCell class]   
        forCellReuseIdentifier:NSStringFromClass([MyCustomCell class])
     ];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView
        dequeueReusableCellWithIdentifier:NSStringFromClass([MyCustomCell class])      
        forIndexPath:indexPath
    ];
    cell.myCustomLabel.text = @"blah";
    return cell;
}

Upvotes: 5

Related Questions