UICollectionView 滚动后不删除旧单元格

Posted

技术标签:

【中文标题】UICollectionView 滚动后不删除旧单元格【英文标题】:UICollectionView not removing old cells after scroll 【发布时间】:2013-07-17 15:51:35 【问题描述】:

我有一个带有图像网格的 UICollectionView。当您点击一个时,它会打开网格并显示一个包含一些细节的子视图。像这样:

我通过调整 UICollectionViewLayoutAttributes 并在 transform3D 属性上为所选项目的当前行下方的所有单元格设置转换来打开 UICollectionViewLayout 中的网格。与我第一次尝试在网格中插入另一个大小与其他单元格不同的单元格相比,它的效果非常好,并且是一个更好的动画和更简单的方法。

无论如何...它大部分时间都可以使用,但是在继续使用后,我会在集合视图上看到旧图像。它们就像幽灵细胞。我无法单击它们,就好像它们没有从集合视图中正确删除,它们位于单元格顶部,防止点击,只是令人讨厌。像这样:

任何想法为什么这些细胞会这样做?

编辑: 我想补充一点,我认为只有当我快速滚动集合视图时才会发生这种情况。我已经编写了自己的 UICollectionViewFlowLayout 替换来测试它是否仍然发生。确实如此。

编辑 2: 3d 变换或布局与此无关。一定是 UICollectionView 的 bug。我可以通过快速滚动、停止然后查询屏幕上的视图来利用。单元格的数量通常是原来的两倍,但它们是隐藏的,因为它们彼此堆叠在一起。由于我所做的翻译,我上面的实现揭示了它们。

这也确实会损害性能。

查看我的答案以获得解决方案。

【问题讨论】:

能发个截图吗? 奇怪的随机时刻,几个小时前就看到了。看了你的个人资料页面。有一个应用程序的想法。去寻找 Flickr API。在 Github 上看到 FlickrKit。认出了这个名字。哈哈!网络接收! 是的,我到处走走......你在利兹,我来自利兹......实际上,我正要开车去那里。 未来从这里增加一些观点:我似乎在 ios 6 下遇到了这个问题,但在 iOS 7 下没有。当单元格应该在 6 下通过动画移动时,它们通常会留下副本;滚动时,如果我在该集合视图单元格的removeFromSuperview 或集合视图的willRemoveSubview: 的创意子类中添加代码以检查是否删除,我看到视图永远不会被删除。在 iOS 7 下我看不到这样的问题。所以我认为Apple已经修复了这个错误。根据迄今为止的 cmets,我觉得我还应该补充:我五年前在约克住了五年。 你也应该看看这篇文章。 ***.com/questions/12927027/… 我认为是同一件事,但更优雅的解决方案。我还没有完全测试。 【参考方案1】:

我对问题的第二次编辑详细说明了为什么会发生这种情况,这是我的解决方法。这不是防弹的,但它适用于我的情况,如果你遇到类似的事情,你可以调整我的解决方案:

- (void) removeNaughtyLingeringCells 

    // 1. Find the visible cells
    NSArray *visibleCells = self.collectionView.visibleCells;
    //NSLog(@"We have %i visible cells", visibleCells.count);

    // 2. Find the visible rect of the collection view on screen now
    CGRect visibleRect;
    visibleRect.origin = self.collectionView.contentOffset;
    visibleRect.size = self.collectionView.bounds.size;
    //NSLog(@"Rect %@", NSStringFromCGRect(visibleRect));


    // 3. Find the subviews that shouldn't be there and remove them
    //NSLog(@"We have %i subviews", self.collectionView.subviews.count);
    for (UIView *aView in [self.collectionView subviews]) 
        if ([aView isKindOfClass:UICollectionViewCell.class]) 
            CGPoint origin = aView.frame.origin;
            if(CGRectContainsPoint(visibleRect, origin)) 
                if (![visibleCells containsObject:aView]) 
                    [aView removeFromSuperview];
                
            
        
    
    //NSLog(@"%i views shouldn't be there", viewsShouldntBeThere.count);

    // 4. Refresh the collection view display
    [self.collectionView setNeedsDisplay];    

- (void) scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate 
    if (!decelerate) 
        [self removeNaughtyLingeringCells];
    


- (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView 
    [self removeNaughtyLingeringCells];

【讨论】:

非常感谢!就我而言,在相同情况下删除Hidden 单元格就可以了。此外,我重现此问题的方法是: 1. 滚动非常快。 2. 等待减速停止。 3. 删除一个单元格。 4. 等待删除动画完成。【参考方案2】:

对bandejapaisa 的进一步评论:仅在iOS 6 下,我发现UICollectionView 也有笨拙的动画过渡的习惯。原始单元格将保留在原处,制作副本,然后对副本进行动画处理。通常在原件之上,但并非总是如此。所以一个简单的边界测试是不够的。

因此,我编写了 UICollectionView 的自定义子类,它执行以下操作:

- (void)didAddSubview:(UIView *)subview

    [super didAddSubview:subview];

    //
    // iOS 6 contains a bug whereby it fails to remove subviews, ever as far as I can make out.
    // This is a workaround for that. So, if this is iOS 6...
    //
    if(![UIViewController instancesRespondToSelector:@selector(automaticallyAdjustsScrollViewInsets)])
    
        // ... then we'll want to wait until visibleCells has definitely been updated ...
        dispatch_async(dispatch_get_main_queue(),
        ^
            // ... then we'll manually remove anything that's a sub of UICollectionViewCell
            // and isn't currently listed as a visible cell
            NSArray *visibleCells = self.visibleCells;
            for(UIView *view in self.subviews)
            
                if([view isKindOfClass:[UICollectionViewCell class]] && ![visibleCells containsObject:view])
                    [view removeFromSuperview];
            
        );
    

很遗憾,“这是 iOS 6”测试不能更直接一点,但它在我的实际代码中隐藏在一个类别中。

【讨论】:

我在快速滚动调用 performBatchUpdate: 动画时看到了这种奇怪的“幽灵”效果,并且知道它是什么。我永远不会想到集合视图正在为当前屏幕上的单元格创建一个新单元格 - 这似乎适得其反。很好,谢谢【参考方案3】:

bandejapaisa 答案的 Swift UICollectionView 扩展版本:

extension UICollectionView 

    func removeNaughtyLingeringCells() 

        // 1. Find the visible cells
        let visibleCells = self.visibleCells()
        //NSLog("We have %i visible cells", visibleCells.count)


        // 2. Find the visible rect of the collection view on screen now
        let visibleRect = CGRectOffset(bounds, contentOffset.x, contentOffset.y)
        //NSLog("Rect %@", NSStringFromCGRect(visibleRect))


        // 3. Find the subviews that shouldn't be there and remove them
        //NSLog("We have %i subviews", subviews.count)
        for aView in subviews 
            if let aCollectionViewCell = aView as? UICollectionViewCell 

                let origin = aView.frame.origin
                if (CGRectContainsPoint(visibleRect, origin)) 
                    if (!visibleCells.contains(aCollectionViewCell)) 
                        aView.removeFromSuperview()
                    
                

            
        

        // 4. Refresh the collection view display
        setNeedsDisplay()
    

【讨论】:

以上是关于UICollectionView 滚动后不删除旧单元格的主要内容,如果未能解决你的问题,请参考以下文章

iOS UICollectionView无限轮播

iOS UICollectionView无限轮播

UICollectionView 水平滚动,删除最后一项,动画不起作用

UICollectionView 在滚动时留下空白单元格

RxSwift - UICollectionView 更新后不更新

UICollectionView 滚动很慢