UICollectionView не удаляет старые ячейки после прокрутки

У меня есть UICollectionView с сеткой изображений. Когда вы нажмете на один, он откроет сетку и покажет subview с некоторыми деталями. Вот так:

Supposed to look like this

Я открываю сетку в своем UICollectionViewLayout, настраивая UICollectionViewLayoutAttributes и устанавливая перевод в свойстве transform3D для всех ячеек ниже текущей строки выбранного элемента. Это работает очень хорошо, и это намного лучшая анимация и более простой подход, чем моя первая попытка вставить другую ячейку в сетку, которая отличается от других.

В любом случае... он работает большую часть времени, но затем, после продолжения использования, я вижу старые изображения в представлении коллекции. Они похожи на призрачные клетки. Я не могу нажимать на них, это похоже на то, что они не были удалены из представления коллекции должным образом, и они сидят поверх ячеек, предотвращая краны и просто раздражая. Вот так:

Problem looks like this

Любые идеи, почему эти клетки делают это?

EDIT: Я бы хотел добавить, я думаю, что это происходит только тогда, когда я быстро просматриваю представление коллекции. Я написал свою собственную замену UICollectionViewFlowLayout для проверки, если это все еще происходит. Оно делает.

ИЗМЕНИТЬ 2: 3D-преобразования или макет не имеют к этому никакого отношения. Это должно быть ошибкой в ​​UICollectionView. Я могу использовать, просто прокручивая очень быстро, позволяя зайти в тупик, а затем запросить представления, которые находятся на экране. Часто бывает вдвое больше клеток, но они скрыты, поскольку они сложены друг на друга. Моя реализация выше показывает их из-за перевода, который я делаю.

Это также может повредить производительность.

См. мой ответ для решения.

Ответы

Ответ 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];
}

Ответ 2

Быстрый дальнейший комментарий к bandejapaisa's: только под 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 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" не может быть немного более прямым, но он скрыт в категории в моем фактическом коде.

Ответ 3

Быстрая версия расширения UICollectionView для ответа bandejapaisa:

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