我能够在一个非常简单的独立应用程序上重现它。
我有一个 collectionView,我想制作循环/循环,以便元素一次又一次地重复(也就是当用户位于数组中的最后一个元素时,它会再次显示第一个元素。如果它们位于第一个和向左滚动,它再次显示最后一个元素)。所以这是一个永无止境的 collectionView。
举个简单的例子,让我们使用星期几:
....周日、周一、周二、周三..周六、周日、周一....
为了实现这一点,我在 numberOfItemsInSection
中返回了一个大数字 (10000),并在 cellForItemAtIndexPath
中使用了 indexPath.item%7
> 调整和获取正确元素的方法。使用 %7
因为有 7 天。
我的单元格非常简单——里面只有一个 UILabel。
这一切都很完美。
sizeForItemAtIndexPath
有问题。我希望细胞适合标签。由于只有 7 个实际大小变化,所以我将 7 天的大小预先缓存在字典中,并在 sizeForItemAtIndexPath
方法中返回正确的大小。
问题是(由于错误或 Apple 对 collectionview 的故意错误设计),sizeForItemAtIndexPath
在 collectionview 出现之前获取每个 indexPath 的调用。因此,如果我想要循环 collectionView 逻辑并需要返回大数字 (10000),它会为所有 10000 个索引调用 sizeForItemAtIndexPath
。所以在 collectionView 出现之前会有几秒钟的延迟。如果我注释掉 sizeForItemAtIndexPath
,它会立即生效。所以这绝对是问题所在。我在 sizeForItemAtIndexPath
中放置了一个 NSLog
,它会在加载前记录所有 22222 调用。
我什至定义了 setEstimatedItemSize
,它仍然为所有索引调用 sizeForItemAtIndexPath
。
我可以通过返回较小的数字 1000 来减少延迟,但这仍然是一个糟糕的设计或错误。
TableView 没有这个错误——你可以定义一百万行,它只在实际需要时调用 heightForRow。所以我不确定为什么 collectionView 需要在显示之前为所有单元格调用它,特别是如果 setEstimatedItemSize
也已经定义。
这个 bug 的另一个副作用是如果我返回一个更大的值,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。
几乎在我研究圆形 ScrollView 的所有地方,他们都建议为此目的使用 Collection View ,因为它更容易。我的实际要求有点复杂,这需要我也使用 collectionview。
Scrollview 需要你使用 scrollViewDidScroll 方法并且每次都改变内容偏移量。此外,它会立即将所有 View 加载到内存中,因为它没有像 collectionview 那样重用现有单元格的优势。所以这是另一个内存点。
7 个工作日是我使用的一个简单示例。如果有人想显示大量数据(100),那在 ScrollView 和 Collection View 中将是一个非常糟糕的实现,将出现此错误。
最佳答案
这是预期的行为,与 UICollectionView
没有太大关系- 更多UICollectionViewFlowLayout
你使用的。
由于每个单元格的大小不同,FlowLayout 将分别请求每个单元格的大小,以计算总大小 CollectionView
- 这是正确处理滚动、滚动条所必需的。
UITableView 它更简单一些,因为它的布局更简单(只有高度很重要)- 这就是为什么可以在那里使用 estimatedSize 的原因。
整体Core Layout Process
在这里有很好的解释:
为了克服这个问题,我建议使用您的自定义 UICollectionViewLayout
并移动您的缓存逻辑并重新使用 CollectionViewLayout
内单元格的大小, 不在你的 ViewController
.
关于ios - 在加载之前为所有索引调用 sizeForItemAtIndexPath - 我很确定我遇到了 UICollectionView 错误或糟糕的设计,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53453681/