Я могу воспроизвести это в очень простом автономном приложении.
У меня есть 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 представит эту ошибку.