在滚动期间重用 UICollectionViewCells
Posted
技术标签:
【中文标题】在滚动期间重用 UICollectionViewCells【英文标题】:Reuse of UICollectionViewCells during scrolling 【发布时间】:2014-02-06 21:45:52 【问题描述】:我有问题,
我有一个简单的 UICollectionView,其中包含从 Flickr 加载图像的静态 200 个单元格。
我的 CellForItemAtIndexPath 如下所示:
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath
UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:@"FlickrCell" forIndexPath:indexPath];
cell.backgroundColor = [self generateRandomUIColor];
if(![[cell.subviews objectAtIndex:0] isKindOfClass:[PFImageView class]])
NSURL *staticPhotoURL = [self.context photoSourceURLFromDictionary:[self.photos objectAtIndex:indexPath.row] size:OFFlickrSmallSize];
PFImageView *imageView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, cell.frame.size.height, cell.frame.size.width) andImageURL:staticPhotoURL andOwningCell:cell];
[cell addSubview:imageView];
return cell;
PFImageView 是UIImageView
的子类,它在后台线程上加载 Flickr 照片 URL,然后在主线程上更新它自己的图像 - 这工作正常。
逻辑非常简单 - 如果没有可出列的单元格,我创建一个单元格。
如果单元格(我希望它被出列并且已经有一个 PFImageView)没有有一个 PFImageView,我为单元格分配和初始化一个 imageView 并将其添加为子视图细胞。
因此我希望如果单元格已出列,它应该已经有一个 PFImageView 作为子视图,因为我们不应该进入 if 语句来创建新的 imageView 并启动新的照片下载请求
相反,我看到的是 UICollectionView 顶部和底部的单元格暂时“离开屏幕” - 当它们返回屏幕时,它们没有被重用,并且似乎创建了一个新单元格并刷新了图片。
1) 如何在创建单元格后获得静态图像(即当单元格稍微离开屏幕时不刷新。
2) 为什么细胞没有被重复使用?
非常感谢您的宝贵时间。
约翰
【问题讨论】:
【参考方案1】:UICollectionView
将重复使用单元以实现最大效率。它不保证任何特定的重用或填充策略。有趣的是,它似乎基于两个区域的整数幂来放置和删除单元格——例如在非视网膜 iPad 上,它可能会将您的滚动区域划分为 1024x1024 的区域,然后在这些区域进出可见区域时填充和取消填充每个区域。但是,您不应该对它的确切行为做出任何预期。
此外,您对集合视图单元格的使用不正确。见the documentation。一个单元格至少有两个子视图——backgroundView
和contentView
。因此,如果您添加一个子视图,它将至少位于索引 2 处,实际上,该索引将是未定义的。在任何情况下,您都应该将子视图添加到contentView
,而不是单元格本身。
最正常的做法是创建一个自定义的 UICollectionView
子类,其中固有地包含一个 PFImageView
。
【讨论】:
【参考方案2】:我发现了几个潜在问题:
您正在专门查看要添加的子类的单元格的索引 0。 UICollectionViewCell 可能有其他视图作为子视图,因此您不能只假设唯一(或第一个)子视图是您添加的视图。
我没有看到您正在调用 registerClass:forCellWithReuseIdentifier: 或 registerNib:forCellWithReuseIdentifier:,其中之一是正确使用 dequeue (https://developer.apple.com/library/ios/documentation/uikit/reference/UICollectionViewCell_class/Reference/Reference.html) 所必需的。
您只是在必须构造 PFImageView 的情况下设置 PFImageView 的 URL。出队可重用视图的想法是,您将只构建所需视图的一小部分,并且 UITableView 将在它们移出屏幕时回收它们。您需要重置所请求的 indexPath 的值,即使您不构造新内容也是如此。
如果您的情况像您描述的那样简单,您可能可以将 PFImageView 添加到出队的 UICollectionView 的 contentView 属性中。
在您的控制器中:
// solve problem 2
[self.collectionView registerClass:[UICollectionViewCell class] forReuseIdentifer:@"FlickrCell"];
在collectionView:cellForItemAtIndexPath
UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:@"FlickrCell" forIndexPath:indexPath];
cell.backgroundColor = [self generateRandomUIColor];
// solve problem 1 by looking in the contentView for your subview (and looping instead of assuming at 0)
PFImageView *pfImageView = nil;
for (UIView *subview in cell.contentView.subviews)
if ([subview isKindOfClass:[PFImageView class]])
pfImageView = (PFImageView *)subview;
break;
NSURL *staticPhotoURL = [self.context photoSourceURLFromDictionary:[self.photos objectAtIndex:indexPath.row] size:OFFlickrSmallSize];
if (pfImageView == nil)
// No PFImageView, create one
// note the use of contentView!
pfImageView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, cell.contentView.frame.size.height, cell.frame.size.width) andImageURL:staticPhotoURL andOwningCell:cell.contentView];
[cell.contentView addSubview:pfImageView];
else
// Already have recycled view.
// need to reset the url for the pfImageView. (Problem 3)
// not sure what PFImageView looks like so this is an e.g. I'd probably remove the
// URL loading from the ctr above and instead have a function that loads the
// image. Then, you could do this outside of the if, regardless of whether you had
// to alloc the child view or not.
[pfImageView loadImageWithUrl:staticPhotoURL];
// if you really only have 200 static images, you might consider caching all of them
return cell;
对于不太简单的情况(例如,我想直观地布置单元格,或者内容中有多个子项),我通常使用 Interface Builder 自定义 UICollectionViewCell。
-
在项目中创建 UICollectionViewCell 的子类(在您的情况下,将其称为 PFImageCell)。
为我想在初始化时更改的视图(在您的情况下为 UIImageView)添加一个 IBOutlet 属性到该子类。
@property (nonatomic, assign) IBOutlet UIImageView *image;
在 Interface Builder 中,为 UITableView 创建一个原型单元格。
在该原型单元格的属性表中,将 UICollectionViewCell 子类标识为类。
在属性表中为原型单元格指定一个标识符(重用标识符)。
将界面构建器中的子视图添加到原型单元格(此处为 UIImageView)。
使用 IB 将 IBOutlet 属性映射到添加的 UIImageView
然后,在 cellForRowAtIndexPath 出队时,将出队结果强制转换为子类 (PFImageCell) 并设置 IBOutlet 属性实例的值。在这里,您将为 UIImageView 加载正确的图像。
【讨论】:
【参考方案3】:我不确定该单元是否被重复使用。它可能正在被重用,但子视图可能不存在。我的建议是创建一个 PFImageViewCollectionViewCell 类(UICollectionViewCell 的子类)并将其注册为 CollectionView Cell 并尝试。如果我需要一个单元格内的子视图,我就是这样做的,并且会这样做。
【讨论】:
【参考方案4】:尝试在这个特定的UIImageView
上添加标签
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath
static int photoViewTag = 54353532;
UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:@"FlickrCell" forIndexPath:indexPath];
cell.backgroundColor = [self generateRandomUIColor];
PFImageView *photoView = [cell.contentView viewWithTag:photoViewTag];
// Create a view
//
if (!photoView)
photoView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, cell.frame.size.height, cell.frame.size.width) andImageURL:staticPhotoURL andOwningCell:cell];
imageView.tag = photoViewTag;
[cell.contentView addSubview:imageView];
// Update the current view
//
else
NSURL *staticPhotoURL = [self.context photoSourceURLFromDictionary:[self.photos objectAtIndex:indexPath.row] size:OFFlickrSmallSize];
photoView.imageURL = staticPhotoURL;
return cell;
我真的建议您创建自己的 UICollectionViewCell
子类。
编辑:另外,请注意我使用了contentView
属性,而不是直接将其添加到单元格中。
【讨论】:
以上是关于在滚动期间重用 UICollectionViewCells的主要内容,如果未能解决你的问题,请参考以下文章
uicollectionview 可重用单元格图像在滚动时加载
[UGUI]ListLayoutGroup--可重用的滚动列表