带有 UIProgressView 的自定义 UICollectionViewCell 在重用单元格上显示 progressView

Posted

技术标签:

【中文标题】带有 UIProgressView 的自定义 UICollectionViewCell 在重用单元格上显示 progressView【英文标题】:Custom UICollectionViewCell with UIProgressView shows progressView on reused cells 【发布时间】:2013-06-04 17:21:19 【问题描述】:

我将UICollectionViewCell 子类化,因为我想在单元格中添加UIImageViewUIProgressView

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

当我触摸一个单元格并调用collectionView:didSelectItemAtIndexPath: 时,我设置cell.progressView.hidden = NO; 并开始下载和更新progressView

但是,当我滚动该单元格时,该单元格被重复使用,progressView 显示在其他单元格上。我尝试了许多不同的方法,只在正确的单元格上显示它,但我尝试过的都没有工作。

是否有更好的方法来做到这一点,例如在prepareForReuse 中做某事?

编辑:按要求提供完整方法

- (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

【问题讨论】:

cellForItemAtIndexPath 为一个特定的索引路径创建和配置单元格。 “...在所有其他单元格上”是什么意思? 我意识到这一点。我的意思是它没有隐藏在从该方法返回的其他单元格中。 很抱歉,我很难理解这个问题。对于出现的每个单元格,应该调用 cellForItemAtIndexPath 并显示或隐藏进度条。什么是“其他细胞”? 在方法 collectionView:cellForItemAtIndexPath: 返回所有单元格之前尝试设置 [cell setNeedsDisplay:YES] 查看 didSelectItemAtIndexPath: 和 cellForItemAtIndexPath: 的完整方法会很有帮助。目前尚不清楚为什么您会期望 [self.progressItems objectForKey:item.name] 对于不同的单元格会有所不同,因为没有与之关联的 indexPath。 【参考方案1】:

应该存储某个项目是否正在下载的信息 在某些数据源(或模型)中而不是在单元格中(视图)。

然后您可以根据该索引处的项目状态更新数据源委托方法collectionView:cellForItemAtIndexPath:中的单元格外观 路径并显示或隐藏单元格的进度视图。

添加:进度、完成和失败块都捕获当前单元格。 因此,即使已将其用于不同的索引路径,他们也会修改此单元格。 为了解决这个问题,您可以检查单元格的(当前)索引路径是否仍然相等 到原始(捕获的)索引路径:

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

【讨论】:

我一直在这样做,只是没有在这里发布,因为它不适合我。请参阅上面的编辑。 @NicHubbard:盯着你的代码看了一段时间后,我有了另一个想法可能是什么问题。我已经更新了答案,但代码没有经过编译器检查。 - 希望对您有所帮助! 这实际上解决了activityView被重用的问题,这是个好消息!但是现在,这样做的一个副作用是,当调用完成块时,它似乎不知道它正在下载哪个单元格,如果我已经将该单元格滚动到屏幕外。因此,如果我返回该单元格,progressView 尚未被删除,imageView 尚未更新为 1.0 alpha。在那里尝试了相同的代码,但在那里似乎没有帮助。 @NicHubbard:也许您可以使用新代码更新问题。不过现在在德国已经很晚了,所以我明天再看看。 @sbru:这无济于事,因为 table view 已经保留了单元格(通过将其放在重用队列中)。问题不在于该块保持对单元格的 strong 引用,而是执行该块时该单元格可能位于不同的位置。【参考方案2】:

我认为您需要做的只是在您的 didSelectRowAtIndexPath 方法的开头引用一次单元格,并创建一个属性来跟踪选定的 indexPath 和进度指示器的值。下面的方法有效,但我必须创建一个虚拟的慢速方法来测试它,所以我希望你能看到如何适应你的问题。

@interface TableController ()
@property (strong,nonatomic) NSArray *theData;
@property (strong,nonatomic) NSIndexPath *downloadingCellPath;
@property (nonatomic) float progValue;
@end

@implementation TableController

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

    RDCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
    cell.label.text = self.theData[indexPath.row];
    if ([self.downloadingCellPath isEqual:indexPath]) 
        cell.progView.hidden = NO;
    else
        cell.progView.hidden = YES;
    
    return cell;


- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

    self.downloadingCellPath = indexPath;
    [self.tableView reloadData];
    [self longMethod:indexPath];



-(void)longMethod:(NSIndexPath *) indexPath 
    UIProgressView *activeProgressView = [(RDCell *)[self.tableView cellForRowAtIndexPath:indexPath] progView];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
        float x = 0;
        for (int i=0; i < 40000000; i++) 
            x = i * 3.14;
        

        dispatch_async(dispatch_get_main_queue(), ^
            self.progValue +=.01;
            activeProgressView.progress = self.progValue;
            if (self.progValue < .99)
                [self longMethod:indexPath];
            else
                NSLog(@"done");
                self.downloadingCellPath = nil;
                [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
            
        );
    );

【讨论】:

是的,但是如果用户想要点击多个单元格来开始下载这些项目,这将不起作用。 @NicHubbard,好吧,在这种情况下,您需要保留一组选定路径而不是单个项目,并检查该数组是否包含Object:indexPath。【参考方案3】:
[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;
    
];

【讨论】:

以上是关于带有 UIProgressView 的自定义 UICollectionViewCell 在重用单元格上显示 progressView的主要内容,如果未能解决你的问题,请参考以下文章

iphone中的自定义UIProgressView

使用带有 GuidedStepSupportFragment 的自定义 XML 布局进行智能电视 UI 设计

如何使用带有 material-ui 图标组件的自定义 SVG 文件?

使用带有 Material UI 的自定义主题在文本字段上指定悬停边框颜色

如何在 Swagger UI 中发送带有请求的自定义标头?

mfc c++ 将带有 postmessage 的自定义用户消息从工作线程发送到主 ui 线程