UICollectionView 在 UICollectionViewCells 上调用 setNeedsDisplay
Posted
技术标签:
【中文标题】UICollectionView 在 UICollectionViewCells 上调用 setNeedsDisplay【英文标题】:UICollectionView calling setNeedsDisplay on UICollectionViewCells 【发布时间】:2013-12-03 10:58:28 【问题描述】:我有一个带有自定义 UICollectionViewCell 类的 UICollectionView,我在其中绘制了必要的内容。然而,我发现当我上下滚动集合视图时,它会重新绘制每个单元格,因为它会滚动并再次回到屏幕上。这会导致非常不稳定的滚动。我认为 UICollectionViewCells 会自动缓存和重用,所以我不明白为什么它会不断重绘它们。
我在自定义 UICollectionViewCell 的子视图的 setNeedsDisplay 中有一个 NSLog 语句,这就是为什么我知道它正在重绘它们。这是我用于绘制信息的 UICollectionView 代码。 (PFObject 只是一个使用 Parse 从数据库中检索的对象)
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
MiniCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"MiniCell" forIndexPath:indexPath];
if (!cell.mini)
PFObject *pfMini = [_searchResults objectAtIndex:indexPath.row];
Mini *mini = [Mini instanceWithPFObject:pfMini];
cell.mini = mini;
cell.backgroundColor = [UIColor grayColor];
return cell;
还有我的自定义单元格。这里的 MiniView 是我创建的用于显示图像的自定义视图,我在其他各种地方都使用它:
- (id)initWithFrame:(CGRect)frame
self = [super initWithFrame:frame];
if (self)
// Initialization code
return self;
- (void)drawRect:(CGRect)rect
[super drawRect:rect];
MiniView *miniView = [[MiniView alloc] initWithFrame:rect];
miniView.mini = _mini;
[self addSubview:miniView];
UIImageView *backgroundImage = [[UIImageView alloc] initWithImage:[self randomBackground]];
self.backgroundView = backgroundImage;
- (UIImage *)randomBackground
int backgroundInt = arc4random()%26 + 8;
UIImage *background = [UIImage imageNamed:[NSString stringWithFormat:@"MainBackground%i", backgroundInt]];
return background;
【问题讨论】:
您的第二个来自 UICollectionViewCell 的代码清单是否有问题? 没错,就是继承自 UICollectionViewCell 的 MiniCell 类 【参考方案1】:您应该将初始化语句放在 initWithFrame: 方法中。
- (id)initWithFrame:(CGRect)frame
self = [super initWithFrame:frame];
if (self)
MiniView *miniView = [[MiniView alloc] initWithFrame:rect];
miniView.mini = _mini;
[self addSubview:miniView];
UIImageView *backgroundImage = [[UIImageView alloc] initWithImage:[self randomBackground]];
self.backgroundView = backgroundImage;
return self;
我不记得在没有实际绘制内容的情况下在 drawRect: 方法中完成的初始化。如果您想实际绘制单元格的内容,您应该使用图形上下文:
UIGraphicsBeginImageContext(self.bounds.size);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *imageOfContextSnapshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
这将获取当前上下文的“快照”。您可以将其视为将对象粘贴到画布上。在你的 drawRect: 方法中调用它应该绘制你的内容。请注意,您应该只更新 drawRect: 方法中的上下文,否则您将收到错误消息。此外,只有在实际调用绘图方法时才使用 drawRect: 方法。在创建新的 UIView 类时,drawRect: 方法甚至声明只有在绘制内容时才应该使用该方法。
有关更多信息,请查看 Apple 的参考资料。他们对图形上下文以及如何使用它有非常透彻的解释。
https://developer.apple.com/library/mac/documentation/graphicsimaging/conceptual/drawingwithquartz2d/dq_context/dq_context.html
编辑 1:
根据单元格的数量,您可能需要考虑不绘制任何内容。 dequeueReusableCellWithIdentifier 已经回收了单元格内容并且做得很好。您应该在必要的同时增加复杂性。虽然这对我来说很容易说,因为我不知道 MiniView 正在/正在做什么。
此外,如果您想要更好的性能,您可能需要考虑特定于对象的绘图方法,而不是使用 renderInContext:。例如,NSSString 有:
-drawAtPoint:forWidth:withFont:
-drawAtPoint:forWidth:withFont:
与渲染整个上下文相比,这些在绘制文本方面表现更好。要绘制图像,您应该调用:
-drawInRect:
这些必须在图形上下文中调用,在您的情况下是在 drawRect: 方法中。
编辑 2:
在init方法中初始化时不会绘制mini,因为当你调用时
PFObject *pfMini = [_searchResults objectAtIndex:indexPath.row];
Mini *mini = [Mini instanceWithPFObject:pfMini];
cell.mini = mini;
cell.backgroundColor = [UIColor grayColor];
在您的视图控制器中,迷你视图已被初始化并作为子视图添加到单元格中,但当分配 MiniView 的迷你 iVar 时,单元格的迷你 iVar 很可能为零。换句话说,当你在 initWithFrame:(CGRect)frame 方法中调用它时:
MiniView *miniView = [[MiniView alloc] initWithFrame:rect];
miniView.mini = _mini;
[self addSubview:miniView];
此时“_mini”为零。在您的 cellForItemAtIndexPath: 方法中,您将分配单元格的 mini,而不是 MiniView 的 mini,因此 MiniView 的 mini 继续为零。您可以在 cellForItemAtIndexPath: 中分配 MiniView 的 mini 而不是单元格的 mini,这应该可以解决它。
【讨论】:
很好的答案,谢谢。更详细地说,MiniView 在其 drawRect 方法中绘制了一个“迷你”。 MiniCell 创建一个 MiniView,因此应该显示与之关联的“迷你”。将初始化代码移至 init 方法是有意义的,但现在单元格中什么也没有出现。在 MiniCell 的 draw rect 方法中绘制当前的图形上下文似乎没有什么区别…… 在性能方面有所作为?正如我在上面的答案中提到的,使用 self.layer renderInContext: 方法并不是对性能最友好的方法。此外,要遵循标准做法,您应该将 MiniView 和背景图像放在视图控制器中的数组中,并在集合视图的cellForItemAtIndexPath:
方法中分配单元格的内容。 MiniView 如何在drawRect:
方法中绘制一个“迷你”?那可能是影响性能的地方。 Mini *mini = [Mini instanceWithPFObject:pfMini];
'mini' 画在哪里?
有所作为,因为它现在不绘制任何东西。 MiniView 通过获取一个 iVar(来自其关联的 mini)来绘制一个 mini,该 iVar 是一个图像文件名数组。然后它将这些组合在 drawRect 方法中(使用图形上下文)。因此,当 MiniCell 创建 MiniView 并将 mini 与它关联时,它应该绘制 Mini。但是,如果我在 init 方法中执行此操作,则不会出现任何内容,并且如果我在 MiniCell 的 drawRect 方法中执行此操作,则会出现。但是因为它不断地重绘它,所以性能很差。我想我需要重新考虑一下!【参考方案2】:
您的单元格应该代表一些存在于您的模型中的数据。
现在我看到您为每个单元格创建了一个随机背景,但可以根据需要创建和销毁单元格,因此您确实应该将此信息存储在某个地方。
一旦您决定将背景存储在例如视图控制器的@property NSArray *backgrounds
中,您就可以使用标准模式:
@interface MyCell : UITableViewCell
@property NSString *backgroundName;
@property (weak) MiniView *miniView;
...
@implementation MyCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier] )
MiniView *miniView = [[MiniView alloc] initWithFrame:rect];
[self addSubview:miniView];
self.miniView = miniView;
// continue creating all necessary subviews ...
- (void)setBackgroundName:(NSString *)name
// assign background
然后当你出列单元格时,你可以使用cell.backgroundName = backgrounds[index]
。
【讨论】:
我支持这一点,正如我在下面的回答中提到的那样,Smikey 应该考虑使用填充单元格的标准方法。【参考方案3】:UICollectionView 仅缓存和重用可见单元格,例如 6 个,即使您有 100 个也是如此。因此,当您滚动时,它会将离开屏幕的单元格和双端队列作为新的单元格。
如果您想将所有单元格保存在内存中,您可以创建单元格
MyCell *cell = [[MyCell alloc] initWithFrame:CGRectMake(...)];
并将它们存储在内部数组中(对于每一行),当代表要求已经创建的单元格时,你给了他一个来自数组的单元格。此解决方案将消耗更多内存,因为所有单元格都将在内存中。
【讨论】:
这将危及视图重用思想背后的基本原因。仅当您的单元格数量非常有限时才这样做( 我知道他可以将有关绘制数据的信息存储在数组(矩形)中,然后像最初一样重绘以上是关于UICollectionView 在 UICollectionViewCells 上调用 setNeedsDisplay的主要内容,如果未能解决你的问题,请参考以下文章
UICollectionView 的 UICollectionReusableView 防止/阻止对 UICollectionViewCell 的点击
根据文本调整单元格宽度 - UICollectionView - iOS