it-swarm.com.ru

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

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

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

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

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

Я могу решить эту проблему как на симуляторе, так и на iPad 3.

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

Illustration of problem and settings


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

77
lindon fox

В реализации layoutAttributesForElementsInRect для UICollectionViewFlowLayout есть ошибка, из-за которой он возвращает два ДВУХ объекта-атрибута для одной ячейки, в некоторых случаях с использованием вставок секций. Один из возвращенных объектов атрибута недействителен (за пределами представления коллекции), а другой является допустимым. Ниже представлен подкласс 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

Смотрите это .

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

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

92
Nick Snyder

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

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

//.h
@interface MainLayout : UICollectionViewFlowLayout
@end

а также

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

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

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

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

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

У меня была эта проблема также для базового макета сетки с вставками для полей. Ограниченная отладка, которую я сделал на данный момент, заключается в реализации - (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;
}

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

5
monowerker

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

4
DrMickeyLauer

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

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

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

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

3
Andrey Seredkin

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

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

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

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

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

3
Masa

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

[self.yourCollectionView reloadData]

с

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

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

2
Yao Li

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

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

Проблема, с которой я столкнулся, заключалась в том, что [super layoutAttributesForElementsInRect: rect] фактически не возвращал все атрибуты UICollectionViewLayoutAttributes, которые должны отображаться на экране. При вызовах [self.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
2
Endama

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

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

Переместив всю логику атрибута в prepare, затем установив их в UICollectionViewCell.apply(), он устранил мерцание и создал отображение гладких клеток масла ????

0
Michael