UICollectionView flowLayout не обертывание ячеек правильно


У меня есть UICollectionView с FLowLayout. Он будет работать так, как я ожидаю большую часть времени, но время от времени одна из ячеек не оборачивается должным образом. Например, ячейка, которая должна быть включена в первом "столбце" третьей строки, если фактически заканчивается во второй строке, и есть только пустое пространство, где оно должно быть (см. диаграмму ниже). Все, что вы можете видеть из этой ячейки румян, - это левая сторона (остальная часть отрезана), и место, где она должна быть, пусто.

Это не происходят последовательно; это не всегда одна и та же строка. Как только это произойдет, я могу прокрутить вверх, а затем обратно, и ячейка будет исправлена. Или, когда я нажимаю на ячейку (что приводит меня к следующему виду с помощью нажатия), а затем поп назад, я увижу ячейку в неправильном положении, а затем она перейдет в правильное положение.

скорость прокрутки, кажется, делает его легче воспроизвести проблему. Когда я медленно прокручиваю, я все еще вижу ячейку в неправильном положении время от времени, но тогда он сразу же перейдет в правильное положение.

проблема началась, когда я добавил разделы вставки. Раньше у меня были ячейки почти заподлицо с границами коллекции (мало или нет вставок), и я не заметил проблемы. Но это означало, что справа и слева от представления коллекции было пусто. Т. е. не удалось прокрутить. Кроме того, полоса прокрутки не была заподлицо справа.

Я могу сделать так, чтобы проблема возникла как на симуляторе, так и на iPad 3.

Я думаю, что проблема происходит из - за левой и правой части вставки... Но если значение неверно, то я ожидаю, что поведение будет последовательным. Интересно, может ли это быть ошибка с Apple? Или, возможно, это связано с наращиванием вставок или чем-то подобным.


последующие: я использую этот ответ ниже ником уже более 2 лет без проблем (в случае, если людям интересно, есть ли никаких дыр в этом ответе-я пока не нашел). Молодец Ник.

11 76

11 ответов:

существует ошибка в реализации uicollectionviewflowlayout layoutAttributesForElementsInRect, которая заставляет его возвращать два объекта атрибутов для одной ячейки в некоторых случаях, связанных с вставками разделов. Один из возвращенных объектов атрибута является недопустимым (за пределами представления коллекции), а другой-допустимым. Ниже приведен подкласс UICollectionViewFlowLayout, который устраняет проблему, исключая ячейки за пределами границ представления коллекции.

// NDCollectionViewFlowLayout.h
@interface NDCollectionViewFlowLayout : UICollectionViewFlowLayout
@end

// NDCollectionViewFlowLayout.m
#import "NDCollectionViewFlowLayout.h"
@implementation NDCollectionViewFlowLayout
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
  NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
  NSMutableArray *newAttributes = [NSMutableArray arrayWithCapacity:attributes.count];
  for (UICollectionViewLayoutAttributes *attribute in attributes) {
    if ((attribute.frame.origin.x + attribute.frame.size.width <= self.collectionViewContentSize.width) &&
        (attribute.frame.origin.y + attribute.frame.size.height <= self.collectionViewContentSize.height)) {
      [newAttributes addObject:attribute];
    }
  }
  return newAttributes;
}
@end

посмотреть этой.

другие ответы предполагают возврат да из shouldinvalidatelayoutforboundsange, но это вызывает ненужные перерасчеты и даже не полностью решает проблему.

мое решение полностью решает ошибку и не должно вызывать никаких проблем, когда Apple устраняет первопричину.

я обнаружил подобные проблемы в моем приложении iPhone. Поиск на форуме Apple dev принес мне это подходящее решение, которое сработало в моем случае и, вероятно, в вашем случае тоже:

подкласс UICollectionViewFlowLayout и заменить shouldInvalidateLayoutForBoundsChange вернуться YES.

//.h
@interface MainLayout : UICollectionViewFlowLayout
@end

и

//.m
#import "MainLayout.h"
@implementation MainLayout
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
    return YES;
}
@end

поместите это в viewController, которому принадлежит представление коллекции

- (void)viewWillLayoutSubviews
{
    [super viewWillLayoutSubviews];
    [self.collectionView.collectionViewLayout invalidateLayout];
}

быстрая версия ответа Ника Снайдера:

class NDCollectionViewFlowLayout : UICollectionViewFlowLayout {
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let attributes = super.layoutAttributesForElements(in: rect)
        let contentSize = collectionViewContentSize
        return attributes?.filter { .frame.maxX <= contentSize.width && .frame.maxY < contentSize.height }
    }
}

у меня была эта проблема, а также для базового макета gridview с вставками для полей. Ограниченная отладка, которую я сделал на данный момент, реализует - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect в моем подклассе UICollectionViewFlowLayout и путем регистрации того, что возвращает реализация суперкласса, что четко показывает проблему.

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSArray *attrsList = [super layoutAttributesForElementsInRect:rect];

    for (UICollectionViewLayoutAttributes *attrs in attrsList) {
        NSLog(@"%f %f", attrs.frame.origin.x, attrs.frame.origin.y);
    }

    return attrsList;
}

введя - (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath Я также вижу, что он, кажется, возвращает неправильные значения для itemIndexPath.item = = 30, что является фактором 10 из числа ячеек моего gridview в строке, а не конечно, если это имеет отношение.

- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
    UICollectionViewLayoutAttributes *attrs = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];

    NSLog(@"initialAttrs: %f %f atIndexPath: %d", attrs.frame.origin.x, attrs.frame.origin.y, itemIndexPath.item);

    return attrs;
}

С нехваткой времени для дополнительной отладки, обходной путь, который я сделал на данный момент, уменьшает ширину моих collectionviews с количеством, равным левому и правому краю. У меня есть заголовок, который все еще нуждается в полной ширине, поэтому я установил clipsToBounds = NO на моем collectionview, а затем также удалил левые и правые вставки на нем, похоже, работает. Для того, чтобы вид заголовка оставался на месте, вам нужно реализовать смещение кадра и размер в методах макета, которые ставится задача вернуть layoutAttributes для представления заголовка.

Я добавил отчет об ошибке в Apple. То, что работает для меня, - это установить нижний sectionInset на значение меньше, чем верхняя вставка.

я испытывал ту же проблему с истощением ячеек на iPhone, используя UICollectionViewFlowLayout и поэтому я был рад найти ваш пост. Я знаю, что у вас возникли проблемы на iPad, но я отправляю это, потому что я думаю, что это общая проблема с UICollectionView. Так вот что я узнал.

Я могу подтвердить, что sectionInset имеет отношение к этой проблеме. Кроме того, что headerReferenceSize также имеет влияние ли клетка истощена или нет. (Это имеет смысл, так как это необходимо для расчета происхождение.)

к сожалению, даже разные размеры экрана должны быть приняты во внимание. Когда я играл со значениями этих двух свойств, я испытал, что определенная конфигурация работала либо на обоих (3.5" и 4"), ни на одном, либо только на одном из размеров экрана. Обычно ни один из них. (Это также имеет смысл, так как границы UICollectionView изменения, поэтому я не испытывал никакого несоответствия между сетчаткой и не-сетчаткой.)

Я закончил установку sectionInset и headerReferenceSize в зависимости от размера экрана. Я попробовал около 50 комбинаций, пока не нашел значения, при которых проблема больше не возникала, и макет был визуально приемлемым. Очень трудно найти значения, которые работают на обоих размерах экрана.

Итак, подводя итог, я просто могу порекомендовать вам поиграть со значениями, проверить их на разных размерах экрана и надеяться, что Apple исправит эту проблему.

Я только что столкнулся с аналогичной проблемой с ячейками, исчезающими после прокрутки UICollectionView на iOS 10 (нет проблем на iOS 6-9).

подкласс UICollectionViewFlowLayout и переопределение метода layoutAttributesForElementsInRect: не работает в моем случае.

решение было достаточно простым. В настоящее время я использую экземпляр UICollectionViewFlowLayout и set и itemSize и estimatedItemSize (я не использовал estimatedItemSize раньше) и установить его на некоторый ненулевой размер. Фактический размер вычисляется в collectionView:layout: sizeForItemAtIndexPath: метод.

кроме того, я удалил вызов метода invalidateLayout из layoutSubviews, чтобы избежать ненужных перезагрузок.

Я только что испытал подобную проблему, но нашел совсем другое решение.

Я использую пользовательскую реализацию UICollectionViewFlowLayout с горизонтальной прокруткой. Я также создаю пользовательские местоположения кадров для каждой ячейки.

проблема, с которой я столкнулся, заключалась в том, что [super layoutAttributesForElementsInRect:rect] на самом деле не возвращал все UICollectionViewLayoutAttributes, которые должны отображаться на экране. На призывы к [собственной личности.collectionView reloadData] некоторые из ячеек внезапно будут установлены в скрытый.

в итоге я создал NSMutableDictionary, который кэшировал все атрибуты UICollectionViewLayoutAttributes, которые я видел до сих пор, а затем включал любые элементы, которые, как я знаю, должны отображаться.

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {

    NSArray * originAttrs = [super layoutAttributesForElementsInRect:rect];
    NSMutableArray * attrs = [NSMutableArray array];
    CGSize calculatedSize = [self calculatedItemSize];

    [originAttrs enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes * attr, NSUInteger idx, BOOL *stop) {
        NSIndexPath * idxPath = attr.indexPath;
        CGRect itemFrame = [self frameForItemAtIndexPath:idxPath];
        if (CGRectIntersectsRect(itemFrame, rect))
        {
            attr = [self layoutAttributesForItemAtIndexPath:idxPath];
            [self.savedAttributesDict addAttribute:attr];
        }
    }];

    // We have to do this because there is a bug in the collection view where it won't correctly return all of the on screen cells.
    [self.savedAttributesDict enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSArray * cachedAttributes, BOOL *stop) {

        CGFloat columnX = [key floatValue];
        CGFloat leftExtreme = columnX; // This is the left edge of the element (I'm using horizontal scrolling)
        CGFloat rightExtreme = columnX + calculatedSize.width; // This is the right edge of the element (I'm using horizontal scrolling)

        if (leftExtreme <= (rect.origin.x + rect.size.width) || rightExtreme >= rect.origin.x) {
            for (UICollectionViewLayoutAttributes * attr in cachedAttributes) {
                [attrs addObject:attr];
            }
        }
    }];

    return attrs;
}

вот категория для NSMutableDictionary, что UICollectionViewLayoutAttributes сохраняются правильно.

#import "NSMutableDictionary+CDBCollectionViewAttributesCache.h"

@implementation NSMutableDictionary (CDBCollectionViewAttributesCache)

- (void)addAttribute:(UICollectionViewLayoutAttributes*)attribute {

    NSString *key = [self keyForAttribute:attribute];

    if (key) {

        if (![self objectForKey:key]) {
            NSMutableArray *array = [NSMutableArray new];
            [array addObject:attribute];
            [self setObject:array forKey:key];
        } else {
            __block BOOL alreadyExists = NO;
            NSMutableArray *array = [self objectForKey:key];

            [array enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *existingAttr, NSUInteger idx, BOOL *stop) {
                if ([existingAttr.indexPath compare:attribute.indexPath] == NSOrderedSame) {
                    alreadyExists = YES;
                    *stop = YES;
                }
            }];

            if (!alreadyExists) {
                [array addObject:attribute];
            }
        }
    } else {
        DDLogError(@"%@", [CDKError errorWithMessage:[NSString stringWithFormat:@"Invalid UICollectionVeiwLayoutAttributes passed to category extension"] code:CDKErrorInvalidParams]);
    }
}

- (NSArray*)attributesForColumn:(NSUInteger)column {
    return [self objectForKey:[NSString stringWithFormat:@"%ld", column]];
}

- (void)removeAttributesForColumn:(NSUInteger)column {
    [self removeObjectForKey:[NSString stringWithFormat:@"%ld", column]];
}

- (NSString*)keyForAttribute:(UICollectionViewLayoutAttributes*)attribute {
    if (attribute) {
        NSInteger column = (NSInteger)attribute.frame.origin.x;
        return [NSString stringWithFormat:@"%ld", column];
    }

    return nil;
}

@end

приведенные выше ответы не работают для меня, но после загрузки изображений, Я заменил

[self.yourCollectionView reloadData]

С

[self.yourCollectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];

чтобы обновить и он может показать все ячейки правильно, вы можете попробовать его.

это может быть немного поздно, но убедитесь, что вы устанавливаете ваши атрибуты в prepare() Если это возможно.

моя проблема заключалась в том, что ячейки выкладывались, а затем получали обновление в layoutAttributesForElements. Это привело к эффекту мерцания, когда новые клетки появились в поле зрения.

перемещая всю логику атрибутов в prepare, а затем установить их в UICollectionViewCell.apply() он устранил мерцание и создал отображение гладкой ячейки масла