我能够在一个非常简单的独立应用程序上重现这一点。
我有一个collectionView,我想让它循环/循环,这样元素就会一次又一次地重复(也就是说,当用户位于数组中的最后一个元素时,它会在之后再次显示第一个元素。如果他们位于第一个元素并向左滚动,它会再次显示最后一个元件(。所以这是一个永无止境的收藏View。
因此,作为一个简单的例子,让我们使用一周中的几天:
星期日、星期一、星期二、星期三。。星期六、星期日、星期一。。。。
为了实现这一点,我在numberOfItemsInSection
中返回一个大数字(10000(,并在cellForItemAtIndexPath
方法中使用indexPath.item%7
来调整并获得正确的元素。使用%7
,因为有7天。我的手机很简单,里面只有一个UILabel
这一切都很完美。
该问题与sizeForItemAtIndexPath
有关。我希望单元格符合标签。由于实际大小只有7个变化,所以我在字典中预先缓存了7天的大小,并在sizeForItemAtIndexPath
方法中返回正确的大小。
问题是(可能是由于collectionview的错误或苹果公司故意设计的错误(,sizeForItemAtIndexPath
在collectionview出现之前会调用每个indexPath。因此,如果我想要循环collectionView逻辑,并且需要返回大数字(10000(,那么它就是为所有10000个索引调用sizeForItemAtIndexPath
。因此,在collectionView出现之前会有几秒钟的延迟。如果我注释掉sizeForItemAtIndexPath
,那么它会立即工作。所以这绝对是问题所在。我在sizeForItemAtIndexPath
中放入了一个NSLog
,它在加载之前记录了所有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可以轻松处理巨大的值,因为它没有这个bug。
我也尝试过禁用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];
}
编辑:
很少有人提到使用滚动视图而不是collectionview。
几乎在我研究循环滚动视图的所有地方,他们都建议为此使用collectionview,因为这要容易得多。我真正的需求有点复杂,这也需要我使用collectionview。
Scrollview要求您使用scrollViewDidSoll方法,并每次更改内容偏移量。此外,它一次将所有视图加载到内存中,因为它不像collectionview那样具有重用现有单元格的优势。这又是一次记忆冲击。
7个工作日是我使用的一个简单的例子。如果有人想显示大量数据(100(,那么滚动视图中的实现将非常糟糕,collectionview将出现此错误。
这是预期的行为,与UICollectionView
没有太大关系,您使用的更多的是UICollectionViewFlowLayout
。
由于每个单元格大小不同,FlowLayout将分别请求每个单元格的大小,以计算CollectionView
的总大小,这是正确处理滚动条所需的。
UITableView它稍微简单一点,因为它的布局要简单得多(只有高度很重要(——这就是为什么可以在那里使用estimatedSize的原因。
整个Core Layout Process
在这里得到了很好的解释:
https://developer.apple.com/library/archive/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/CreatingCustomLayouts/CreatingCustomLayouts.html
为了克服这个问题,我建议使用您的自定义UICollectionViewLayout
,移动您的缓存逻辑,并为CollectionViewLayout
中的单元格重用大小,而不是ViewController
中的单元格。