Melany
Melany

Reputation: 466

UITableView after prepareForReuse - all the data in Cell disappear

I have a TableView with customCell, that has next properties:

@interface CustomTableViewCell : UITableViewCell

@property (weak, nonatomic) IBOutlet UIImageView *image;
@property (weak, nonatomic) IBOutlet UILabel *nameOfImage;
@property (weak, nonatomic) IBOutlet UIButton *start;
@property (weak, nonatomic) IBOutlet UIButton *stop;
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;
@property (weak, nonatomic) IBOutlet UILabel *realProgressStatus;

In table view - when user press start button - an image is downloaded, as well as progressView with realProgressStatus reflects current situation. At this stage everything works perfect, but when scroll table view and return back to the cell - that was already fulfilled with data - all info disappeared(except the nameOfImage, I set it separately). I implemented next method in my CustomTableViewCell class:

 -(void)prepareForReuse
{
    [super prepareForReuse];

    self.progressView.progress = 0.1;
    self.realProgressStatus.text = @"";
    self.image.image = [UIImage imageNamed:@"placeholder"];
}

I implemented new property NSMutableSet self.tagsOfCells, where I save number of cells where images where already downloaded. I tried to make changes in TableView method but effect is the same :

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString* PlaceholderCellIdentifier = @"Cell";
    CustomTableViewCell *customCell = [tableView dequeueReusableCellWithIdentifier:PlaceholderCellIdentifier forIndexPath:indexPath];
    customCell.delegate = self;
    customCell.cellIndex = indexPath.row;
    if (!customCell)
    {
        customCell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                                reuseIdentifier:@"cell"];
    }
    else
    {
           NSNumber *myNum = [NSNumber numberWithInteger:indexPath.row];

        if (![self.tagsOfCells containsObject:myNum])
        {
            NSLog(@"Row: %ld",(long)indexPath.row);
            UIImage *img = [UIImage imageNamed:@"placeholder"];
            customCell.realProgressStatus.text = @"";
            customCell.progressView.progress = 0.1;
            customCell.image.image = img;
            [customCell setNeedsLayout];
        }
    }

    if ( indexPath.row % 2 == 0 )
        customCell.backgroundColor = [UIColor grayColor];
    else
        customCell.backgroundColor = [UIColor darkGrayColor];

    customCell.nameOfImage.text = self.names[indexPath.row];
    return customCell;
}

EDIT:

I populate self.tagsOfCells in one of methods during downloading images, here is the method:

-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    self.customCell.realProgressStatus.text = @"Downloaded";

    UIImage *img = [UIImage imageWithData:self.imageData];
    self.customCell.image.image = img;
    self.customCell.tag = self.selectedCell;
    [self.savedImages setObject:img forKey:self.customCell.nameOfImage.text];
    NSNumber *myNum = [NSNumber numberWithInteger:self.selectedCell];
    [self.tagsOfCells addObject:myNum];

}

Thanks in advance for any help or advice.

Upvotes: 1

Views: 2507

Answers (3)

malhal
malhal

Reputation: 30617

It looks like you are not using interface builder's prototype cells so you are using the wrong dequeue reusable cell method. The correct one in your situation does not have an index path param e.g.

CustomTableViewCell *customCell = [tableView dequeueReusableCellWithIdentifier:PlaceholderCellIdentifier];

And as the others said you need to move your 2 lines of initialization after you alloc init your custom cell because it's nil before then. Eg you need to dequeue the cell and if nil create one. Then set your params on the cell now you have a valid instance.

Upvotes: 0

jrhee17
jrhee17

Reputation: 1152

Content-related actions shouldn't occur in the prepareForReuse function.

https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableViewCell_Class/#//apple_ref/occ/instm/UITableViewCell/prepareForReuse

If a UITableViewCell object is reusable—that is, it has a reuse identifier—this method is invoked just before the object is returned from the UITableView method dequeueReusableCellWithIdentifier:. For performance reasons, you should only reset attributes of the cell that are not related to content, for example, alpha, editing, and selection state. The table view's delegate in tableView:cellForRowAtIndexPath: should always reset all content when reusing a cell. If the cell object does not have an associated reuse identifier, this method is not called. If you override this method, you must be sure to invoke the superclass implementation.

Edited

Alfie is right, prepareForReuse isn't invoked after returning from cellForRowAtIndexPath

First thing I would try is edit the code

if (!customCell)
{
    customCell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                            reuseIdentifier:@"cell"];
}
else
{
       NSNumber *myNum = [NSNumber numberWithInteger:indexPath.row];

    if (![self.tagsOfCells containsObject:myNum])
    {
        NSLog(@"Row: %ld",(long)indexPath.row);
        UIImage *img = [UIImage imageNamed:@"placeholder"];
        customCell.realProgressStatus.text = @"";
        customCell.progressView.progress = 0.1;
        customCell.image.image = img;
        [customCell setNeedsLayout];
    }
}

to the following

if (!customCell)
{
    customCell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                            reuseIdentifier:@"cell"];
}

NSNumber *myNum = [NSNumber numberWithInteger:indexPath.row];

if (![self.tagsOfCells containsObject:myNum])
{
    NSLog(@"Row: %ld",(long)indexPath.row);
    UIImage *img = [UIImage imageNamed:@"placeholder"];
    customCell.realProgressStatus.text = @"";
    customCell.progressView.progress = 0.1;
    customCell.image.image = img;
    [customCell setNeedsLayout];
}

This really isn't an expensive operation and it's better to have this logic in a single place. Especially since you got rid of prepareForReuse, this should be changed.

The reason when you scroll away, and then come back to not find your data is cellForRowAtIndexPath is being called again, which results in the default data overriding the new data you just added.

You would want to have an underlying data structure and retrieve data from there.


What I would do:

For instances, you can keep a mutable list as such

NSMutableArray* tableData = [[NSMutableArray alloc] init];

add image data for each row to the array

[tableData add:imageData];

and then display it add cellForRowAtIndexPath

customCell.image.image = [tableData objectAtIndex:[indexPath row]];

This would be the normal practice for populating a UITableView with data. This way, even if you scroll away tableData would remain the same, resulting in consistent data.

Note

I would also call new data asynchronously using NSURLSession from within the cell if I had to implement this. Just a suggestion=)

Upvotes: 3

Alfie Hanssen
Alfie Hanssen

Reputation: 17094

I don't think this has anything to do with your use of prepareForReuse. A couple things look fishy.

  1. You're setting the delegate and cellIndex properties on your cell before you know whether it's nil or not. If it's nil then these properties will never be set.

  2. I also don't think you need the else clause in there. The logic that it wraps doesn't have anything to do with whether a cell could be dequeued. it has to do with configuring a non-nil cell.

  3. A missing piece from your question is where do you populate the tagsOfCells object? That code is important. If you're not setting the tag in cellForRowAtIndexPath it seems like your data will get out of sync and this will cause your cell to be misconfigured.

Try the following code:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString* PlaceholderCellIdentifier = @"Cell";
    CustomTableViewCell *customCell = [tableView dequeueReusableCellWithIdentifier:PlaceholderCellIdentifier forIndexPath:indexPath];
    if (!customCell)
    {
        customCell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                            reuseIdentifier:@"cell"];
    }

    NSNumber *myNum = [NSNumber numberWithInteger:indexPath.row];

    if (![self.tagsOfCells containsObject:myNum])
    {
        NSLog(@"Row: %ld",(long)indexPath.row);
        UIImage *img = [UIImage imageNamed:@"placeholder"];
        customCell.realProgressStatus.text = @"";
        customCell.progressView.progress = 0.1;
        customCell.image.image = img;
        [customCell setNeedsLayout];
    }

    customCell.delegate = self;
    customCell.cellIndex = indexPath.row;
    customCell.nameOfImage.text = self.names[indexPath.row];

    if (indexPath.row % 2 == 0)
    {
        customCell.backgroundColor = [UIColor grayColor];
    }
    else
    {
        customCell.backgroundColor = [UIColor darkGrayColor];
    }

    return customCell;
}

Edit

Regarding info in another answer here. prepareForReuse is not being called after you configure your cell. It's being called when you invoke dequeueReusableCell and hence before you configure your cell. I don't believe prepareForReuse is the problem.

Upvotes: 1

Related Questions