带有 UIProgressView 的自定义 UICollectionViewCell 在重用单元格上显示 progressView
Posted
技术标签:
【中文标题】带有 UIProgressView 的自定义 UICollectionViewCell 在重用单元格上显示 progressView【英文标题】:Custom UICollectionViewCell with UIProgressView shows progressView on reused cells 【发布时间】:2013-06-04 17:21:19 【问题描述】:我将UICollectionViewCell
子类化,因为我想在单元格中添加UIImageView
和UIProgressView
:
- (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的主要内容,如果未能解决你的问题,请参考以下文章
使用带有 GuidedStepSupportFragment 的自定义 XML 布局进行智能电视 UI 设计
如何使用带有 material-ui 图标组件的自定义 SVG 文件?