sizeForItemAtIndexPath вызывается для всех индексов перед загрузкой - я почти уверен, что столкнулся с ошибкой UICollectionView ИЛИ плохим дизайном - PullRequest
0 голосов
/ 24 ноября 2018

Я могу воспроизвести это в очень простом автономном приложении.

У меня есть collectionView, который я хочу сделать циклическим / цикличным, чтобы элементы повторялись снова и снова (иначе, когда пользователь находится в последнем элементе в массиве, он снова показывает первый элемент после. И если они находятся впервый и прокрутите влево, он снова показывает последний элемент).Так что это бесконечный collectionView.

Итак, для простого примера, давайте использовать дни недели:

.... Воскресенье, понедельник, вторник, среда ... Суббота, воскресенье, понедельник....

Чтобы добиться этого, я возвращаю большое число (10000) в numberOfItemsInSection и использую indexPath.item%7 в методе cellForItemAtIndexPath для настройки и получения правильного элемента.Используя %7, поскольку есть 7 дней.Мои клетки очень просты - просто UILabel в нем.

Все это прекрасно работает.

Проблема связана с sizeForItemAtIndexPath.Я хочу, чтобы клетки соответствовали этикетке.Поскольку будет всего 7 вариантов фактического размера, я предварительно кэширую размеры 7 дней в словаре и возвращаю правильный размер в методе sizeForItemAtIndexPath.

Проблема в том, что (либо из-заошибка или преднамеренный плохой дизайн Apple представления collectionview), sizeForItemAtIndexPath получает вызовы для каждого indexPath до появления viewview.Поэтому, если я хочу иметь круговую логику collectionView и мне нужно вернуть большое число (10000), он вызывает sizeForItemAtIndexPath для всех 10000 индексов.Таким образом, есть задержка в пару секунд, пока не появится collectionView.Если я закомментирую sizeForItemAtIndexPath, то он работает мгновенно.Так что это определенно проблема.Я вставил NSLog в sizeForItemAtIndexPath, и он записывает все 22222 вызова перед загрузкой.

Я даже определил setEstimatedItemSize, он по-прежнему вызывает sizeForItemAtIndexPath для всех индексов.

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

TableView не имеет этой ошибки - вы можете определить миллион строк, и он вызывает heightForRow только тогда, когда это действительно необходимо.Поэтому я не уверен, почему collectionView должен вызывать его для всех ячеек перед отображением, особенно если setEstimatedItemSize уже определено.

Еще один побочный эффект этой ошибки - то, что collectionView выдает ошибку, если я возвращаюсьбольшее значение (50000 ломает, 22222 в порядке).Он печатает ошибку для слишком больших значений:

This NSLayoutConstraint is being configured with a constant that exceeds internal limits.  A smaller value will be substituted, but this problem should be fixed. Break on BOOL _NSLayoutConstraintNumberExceedsLimit(void) to debug.  This will be logged only once.  This may break in the future.

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

Я также пытался отключить prefetching, но этобез эффекта.

Что вы все думаете?

Соответствующий код:

#define kInfiniteCount 22222
#define kDayNameMargin 30

@interface ViewController (){
    NSMutableDictionary *dictOfSizes;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
    self.myCalendar = [NSCalendar currentCalendar];
    [self.myCalendar setLocale:locale];

    dictOfSizes = [NSMutableDictionary new];

    for (int i=0; i<7; i++) {
        WeekdayCollectionViewCell *sizingCell = [[NSBundle mainBundle] loadNibNamed:@"WeekdayCell" owner:self options:nil][0];
        sizingCell.myLabel.text=[self.myCalendar weekdaySymbols][i];
        [sizingCell layoutIfNeeded];

        [dictOfSizes setObject:[NSValue valueWithCGSize:[sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]] forKey:sizingCell.myLabel.text];
    }



    self.myCollectionView.decelerationRate = UIScrollViewDecelerationRateFast;
    [self.myCollectionView registerNib:[UINib nibWithNibName:@"WeekdayCell" bundle:nil] forCellWithReuseIdentifier:@"daycell"];
    [(UICollectionViewFlowLayout*)self.myCollectionView.collectionViewLayout setEstimatedItemSize:CGSizeMake(200, self.myCollectionView.frame.size.height)];
    [self.myCollectionView reloadData];


    NSInteger middleGoTo = kInfiniteCount/2;

    while (![[self.myCalendar weekdaySymbols][middleGoTo%7] isEqualToString:@"Monday"]) {
        middleGoTo--;
    }

    [self.myCollectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:middleGoTo inSection:0] atScrollPosition:UICollectionViewScrollPositionLeft animated:NO];
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    return kInfiniteCount;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    WeekdayCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"daycell" forIndexPath:indexPath];

    cell.myLabel.text=[self.myCalendar weekdaySymbols][indexPath.item%7];
    return cell;
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
    NSLog(@"sizeForItemAtIndexPath: %ld",indexPath.item);
    return [(NSValue*)[dictOfSizes objectForKey:[self.myCalendar weekdaySymbols][indexPath.item%7]] CGSizeValue];
}

РЕДАКТИРОВАТЬ:

Мало кто упомянул об использовании scrollview вместо collectionview для этого.

Практически везде, где я исследовал круговое представление прокрутки, они рекомендовали использовать для этой цели коллекционное представление, поскольку это намного проще.Мое реальное требование немного сложнее, и я тоже должен использовать collectionview.

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

7 дней недели - простой пример, который я использовал.Если кто-то хочет показать много данных (100), это будет довольно плохой реализацией в scrollview, и collectionview представит эту ошибку.

1 Ответ

0 голосов
/ 24 ноября 2018

Это ожидаемое поведение, и оно не столько связано с UICollectionView - оно больше UICollectionViewFlowLayout, которое вы используете.

Если каждая ячейка имеет разный размер, FlowLayout будет запрашивать размеры для каждой ячейки отдельно, чтобырассчитать общий размер CollectionView - это требуется для правильной обработки прокрутки, полос прокрутки.

UITableView это немного проще, так как его компоновка намного проще (важны только высота) - поэтому там можно использовать оцененный размер.

Все Core Layout Process хорошо объяснено здесь:

https://developer.apple.com/library/archive/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/CreatingCustomLayouts/CreatingCustomLayouts.html

Чтобы преодолеть эту проблему, я бы рекомендовал использовать пользовательский UICollectionViewLayout и переместить логику кэширования.и повторное использование размеров для ячеек внутри CollectionViewLayout, а не в вашем ViewController.

...