UICollectionView flowLayout неправильно загортає клітинки


79

У мене є UICollectionViewз FLowLayout. Це спрацює, як я очікую, більшу частину часу, але час від часу одна з комірок не загортається належним чином. Наприклад, комірка, яка повинна бути в першому "стовпці" третього рядка, якщо насправді відстає у другому рядку, і там просто має бути порожній простір (див. Схему нижче). Все, що ви можете побачити в цій рум’яній комірці - це ліва сторона (решта відрізана), а місце, де воно повинно бути, порожнє.

Це відбувається не послідовно; це не завжди однаковий рядок. Як тільки це станеться, я можу прокрутити вгору, а потім назад, і клітина сама зафіксується. Або, коли я натискаю клітинку (яка переводить мене до наступного подання за допомогою натискання), а потім повертається назад, я бачу клітинку в неправильному положенні, а потім вона перескакує у правильне положення.

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

Проблема почалася, коли я додав вставки розділів. Раніше у мене клітини майже змивалися з межами збору (мало або взагалі не було вставок), і я не помітив проблеми. Але це означало, що праворуч і ліворуч від подання колекції було порожньо. Тобто, не вдалося прокрутити. Крім того, смужка прокрутки не знаходилася вліво вправо.

Я можу зробити проблему як на Simulator, так і на iPad 3.

Я думаю, проблема виникає через вставки лівого та правого розділів ... Але якщо значення неправильне, то я би очікував, що поведінка буде послідовною. Цікаво, чи це не може бути помилка з Apple? Або, можливо, це пов’язано з накопиченням вставок або чимось подібним.

Ілюстрація проблеми та налаштувань


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

Відповіді:


94

Існує помилка в реалізації 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

Дивіться це .

Інші відповіді пропонують повернути ТАК із shouldInvalidateLayoutForBoundsChange, але це спричиняє непотрібні перерахунки і навіть не вирішує проблему повністю.

Моє рішення повністю вирішує помилку і не повинно викликати проблем, коли Apple виправляє першопричину.


5
FYI ця помилка також виникає при горизонтальній прокрутці. Заміна x на y та ширину на висоту змушує цей патч працювати.
Патрік Тешер,

Дякую! Я тільки починав грати з collectionView (якщо ви хочете спамувати Apple з цього приводу, ось посилання на rdar openradar.appspot.com/12433891 )
Vinzzz

1
@richarddas Ні, ви не хочете перевіряти, чи перетинаються прямокутні. Насправді всі комірки (дійсні чи недійсні) перетинатимуть прямі межі подання колекції. Ви хочете перевірити, чи не потрапляє якась частина прямокутника за межі, що і робить мій код.
Нік Снайдер,

2
@Rpranata На iOS 7.1 ця помилка не виправлена. Зітхайте.
nonamelive

2
Можливо, я використовую це неправильно, але на iOS 8.3 у Swift це спричиняє відсутність взаємних переглядів праворуч, які раніше були відрізані. Будь-хто інший?
sudo

8

Помістіть це у viewController, якому належить подання колекції

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

Куди ти це кладеш?
fatuhoku

Into the viewController, якому належить подання колекції
Peter Lapisu

3
Моє питання полягало в тому, що клітини повністю зникли. Це рішення допомогло - однак це спричиняє непотрібне перезавантаження. Досі це працює зараз .. thx!
pawi

1
це викликає нескінченний цикл, якщо для мене телефонує viewController
Хофі

Як зазначає DHennessy13 , це поточне рішення є хорошим, але може бути недосконалим, оскільки призведе до недійсності Макету при обертанні екрана (а в більшості випадків не повинно). Поліпшенням може стати встановлення прапора invalidateLayoutлише один раз.
Cœur,

7

я виявив подібні проблеми у своєму додатку iPhone. Пошук на форумі розробників Apple приніс мені таке підходяще рішення, яке спрацювало в моєму випадку, і, мабуть, також у вашому випадку:

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

//.h
@interface MainLayout : UICollectionViewFlowLayout
@end

і

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

Це лише частково вирішує проблему, яку я маю з цією проблемою. Клітина справді переходить у правильне місце, коли з’являється рядок. Однак безпосередньо перед тим, як він з’являється, я все ще отримую клітинку, що з’являється збоку.
Деніел Вуд,

Обережно - виконуючи це, макет запускатиметься кожного разу під час прокрутки. Це може сильно вплинути на продуктивність.
fatuhoku

7

Швидка версія відповіді Ніка Снайдера:

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

1
це повністю змусило CollectionViewCell зникнути ... Будь-яке інше можливе рішення?
Гіггз,

5

У мене була така проблема і для базового макета 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;
}

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


Дякуємо за додаткову інформацію @monowerker. Я думаю, моя проблема почалася, коли я додав вставки (я додав це до питання). Я спробую ваші методи налагодження і переконаюся, чи це мені щось говорить. Я, можливо, теж спробую твою роботу.
Lindon Fox

Це, швидше за все, помилка в UICFL / UICL, я спробую отримати радар, коли у мене буде час, ось обговорення з деякими номерами rdar, на які ви можете посилатися. twitter.com/steipete/status/258323913279410177
monowerker

4

Я додав звіт про помилки в Apple. Що мені підходить, так це встановити нижній sectionInset на значення менше верхнього.


3

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

Я можу підтвердити, що sectionInsetце стосується цієї проблеми. Крім того, це headerReferenceSizeтакож впливає на те, чи клітина деплагована чи ні. (Це має сенс, оскільки це потрібно для обчислення походження.)

На жаль, потрібно враховувати навіть різні розміри екрану. Погравши зі значеннями для цих двох властивостей, я відчув, що певна конфігурація працювала або на обох (3,5 "і 4"), і на жодному, або лише на одному з розмірів екрану. Зазвичай жоден з них. (Це також має сенс, оскільки межі UICollectionViewзмін, отже, я не зазнав жодної диспропорції між сітківкою та не сітківкою.)

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

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


3

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

Підклас UICollectionViewFlowLayout та перевизначення методу layoutAttributesForElementsInRect: у моєму випадку не працює.

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

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


де встановив sizesize and ratedItemsize у uicollectionviewflowlayout?
Гаррет Кокс

UICollectionViewFlowLayout * flowLayout = [[UICollectionViewFlowLayout alloc] init]; [flowLayout setItemSize: CGSizeMake (200, 200)]; [flowLayout setEstimatedItemSize: CGSizeMake (200, 200)]; self.collectionView = [[UICollectionView alloc] initWithFrame: CGRectZero collectionViewLayout: flowLayout];
Андрій Середкін

Встановлення оціночногоItemSize зробив це для мене
wmurmann

2

Я щойно випробував подібну проблему, але знайшов зовсім інше рішення.

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

Проблема, з якою я стикалася, полягала в тому, що [super layoutAttributesForElementsInRect: rect] насправді не повертав усі UICollectionViewLayoutAttributes, які повинні відображатися на екрані. Під час викликів [self.collectionView reloadData] для деяких клітинок раптом буде встановлено прихованість.

Що я в підсумку зробив, це створив NSMutableD Dictionary, який кешував усі 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

Я також використовую горизонтальну прокрутку, мені вдається виправити проблему за допомогою вашого рішення, але після переходу до іншого перегляду та повернення, розмір вмісту здавався неправильним, коли є зайві елементи, які не поділяються на стовпці однаково.
morph85

Я знайшов рішення, щоб виправити проблему, коли клітина приховувалася після виконання перегляду та повернення до подання колекції. Постарайтеся не встановлювати оценкиItemSize в collectionViewFlowLayout; встановити безпосередньо Розмір.
morph85

2

Наведені вище відповіді для мене не працюють, але після завантаження зображень я замінив

[self.yourCollectionView reloadData]

з

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

оновити, і він може відображати всі клітинки правильно, ви можете спробувати.


0

Це може бути трохи пізно, але переконайтеся, що ви встановлюєте свої атрибути, prepare()якщо це можливо.

Моя проблема полягала в тому, що клітини викладали, а потім отримували оновлення layoutAttributesForElements. Це призвело до ефекту мерехтіння, коли з’явилися нові клітини.

Перемістивши всю логіку атрибутів prepare, потім встановивши їх у UICollectionViewCell.apply()ньому, усунуло мерехтіння і створило гладку клітинку, що відображає 😊

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.