ios - 在加载之前为所有索引调用 sizeForItemAtIndexPath - 我很确定我遇到了 UICollectionView 错误或糟糕的设计

标签 ios objective-c swift uicollectionview uicollectionviewlayout

我能够在一个非常简单的独立应用程序上重现它。

我有一个 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在这里有很好的解释:

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

为了克服这个问题,我建议使用您的自定义 UICollectionViewLayout并移动您的缓存逻辑并重新使用 CollectionViewLayout 内单元格的大小, 不在你的 ViewController .

关于ios - 在加载之前为所有索引调用 sizeForItemAtIndexPath - 我很确定我遇到了 UICollectionView 错误或糟糕的设计,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53453681/

相关文章:

android - 在 Chromecast 上播放 Widevine DRM

ios - 如何将项目排列成表格 View 中的部分?

ios - Swift raptureXML

iphone - Objective-C 中的纺织品解析?

swift - SwiftUI 中的 ImagePicker

ios - Alamofire - Swift 编译器错误 - 使用未声明的类型

ios - Swift - 发送到已释放对象的消息 - 快速优化级别最快

iphone - 异步函数执行?

iphone - NSSortDestiptor 按 plist 编号排序时出错

ios - 如何使用带有项目格式的约束在 ScrollView 上添加容器 View