Nic Hubbard Nic Hubbard - 2 months ago 18
Objective-C Question

Custom UICollectionViewCell with UIProgressView shows progressView on reused cells

I am subclassing

UICollectionViewCell
because I wanted to add a
UIImageView
and a
UIProgressView
to the cell:

- (id)initWithFrame:(CGRect)frame {

self = [super initWithFrame:frame];
if (self) {

// ImageView
_imageView = [[UIImageView alloc] initWithFrame:self.bounds];
_imageView.layer.borderColor = [UIColor whiteColor].CGColor;
_imageView.layer.borderWidth = 0.7;
[self.contentView addSubview:_imageView];

// Progress View
_progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleBar];
_progressView.frame = CGRectMake(5, self.bounds.size.height - 20, self.bounds.size.width - 10, 10);
_progressView.hidden = YES;
[self.contentView addSubview:_progressView];

}
return self;
}


When I touch a cell and it calls
collectionView:didSelectItemAtIndexPath:
I set
cell.progressView.hidden = NO;
and start my download and updating of the
progressView
.

But, as I scroll that cell is reused and the
progressView
is shown on other cells. I have tried a number of different things to only show it on the correct cell, but nothing I have tried is working.

Is there a better way to do this such as doing something in
prepareForReuse
?


EDIT: Full methods as requested

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
PUCViewpointItem *item = [self.items objectAtIndex:indexPath.row];
PUCImageGridCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CollectionViewCellIdentifier forIndexPath:indexPath];
[cell.imageView setImageWithURL:[NSURL URLWithString:item.imageURLHigh] placeholderImage:[UIImage imageNamed:@"PUCDefaultBackground.png"]];

// See if we have the file already
if (![self.itemPaths objectForKey:item.name]) {
cell.imageView.alpha = 0.4;
} else {
cell.imageView.alpha = 1.0;
}

// See if we are downloading
if (![self.progressItems objectForKey:item.name]) {
cell.progressView.hidden = YES;
} else {
cell.progressView.hidden = NO;
}

return cell;
}


- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {

PUCViewpointItem *item = [self.items objectAtIndex:indexPath.row];
PUCImageGridCell *cell = (PUCImageGridCell *)[collectionView cellForItemAtIndexPath:indexPath];

// File Path
NSString *path = [self itemPath:item];
if (!path) {

// Set the indexPath we are downloading
[self.progressItems setObject:indexPath forKey:item.name];

Utility *utility = [[Utility alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:item.url]];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];

NSString *localPath = [[utility localDirectory] stringByAppendingFormat:@"/%@.pdf", item.name];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:localPath append:NO];

[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {

float totalProgress = (float)totalBytesRead/(float)totalBytesExpectedToRead;
if ([[collectionView indexPathForCell:cell] isEqual:indexPath]) {
cell.progressView.alpha = 1.0;
cell.progressView.progress = totalProgress;
}

}];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {

// This section seems to not get called or updated correctly when the cell
// that is showing the activityView is offscreen
if ([[collectionView indexPathForCell:cell] isEqual:indexPath]) {
[self.itemPaths setObject:localPath forKey:item.name];
[self.progressItems removeObjectForKey:item.name];
cell.imageView.alpha = 1.0;
cell.progressView.alpha = 0.0;
}

} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Error: %@", error);
[self.progressItems removeObjectForKey:item.name];
cell.progressView.alpha = 0.0;
}];

[operation start];

} else {

[self readIssueAtPath:path];

}

}//end

Answer

The information whether a download is in progress for a certain item or not should be stored in some data source (or model) and not in the cell (the view).

Then you can update the cell's appearance in the data source delegate method collectionView:cellForItemAtIndexPath: according to the status of the item at that index path and show or hide the progress view of the cell.

ADDED: The progress, completion and failure block all capture the current cell. Therefore they will modify this cell even if it has been reused for a different index path. To solve that, you can check if the cell's (current) index path is still equal to the original (captured) index path:

[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {

    float totalProgress = (float)totalBytesRead/(float)totalBytesExpectedToRead;
    if ([[collectionView indexPathForCell:cell] isEqual:indexPath]) {
       cell.progressView.alpha = 1.0;
       cell.progressView.progress = totalProgress;
    }
}];

and similar for the completion and failure block:

[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
    [self.itemPaths setObject:localPath forKey:item.name];
    [self.progressItems removeObjectForKey:item.name];
    if ([[collectionView indexPathForCell:cell] isEqual:indexPath]) {
        cell.imageView.alpha = 1.0;
        cell.progressView.alpha = 0.0;
    }

} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    NSLog(@"Error: %@", error);
    [self.progressItems removeObjectForKey:item.name];
    if ([[collectionView indexPathForCell:cell] isEqual:indexPath]) {
        cell.progressView.alpha = 0.0;
    }
}];
Comments