sizeForItemAtIndexPath 在加载之前为所有索引调用 - 我很确定我要么遇到了 UICollectionView 错误,要么设计不佳 [英] sizeForItemAtIndexPath getting called for all indexes before load - I am pretty sure I am either running into a UICollectionView bug OR a poor design

查看:83
本文介绍了sizeForItemAtIndexPath 在加载之前为所有索引调用 - 我很确定我要么遇到了 UICollectionView 错误,要么设计不佳的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am able to reproduce this on a very simple standalone app.

我有一个 collectionView,我想制作循环/循环,因此元素一次又一次地重复(也就是当用户位于数组中的最后一个元素时,它会再次显示第一个元素.如果它们在第一个和向左滚动,它再次显示最后一个元素).所以它是一个永无止境的 collectionView.

I have a collectionView which I want to make circular/loop so the elements repeat again and again (aka when user is at the last element in array, it shows the first element again after. And if they are at the first and scroll left, it shows the last element again). So it's a never ending collectionView.

举个简单的例子,让我们使用星期几:

So for a simple example, let's use days of week:

....周日、周一、周二、周三...周六、周日、周一....

....Sunday, Monday, Tuesday, Wednesday..Saturday, Sunday, Monday....

为了实现这一点,我在 numberOfItemsInSection 中返回一个大数字 (10000) 并在 cellForItemAtIndexPath 中使用 indexPath.item%7> 调整和获取正确元素的方法.使用 %7 因为有 7 天.我的单元格非常简单——里面只有一个 UILabel.

In order to achieve this, I return a big number (10000) in the numberOfItemsInSection and use indexPath.item%7 in the cellForItemAtIndexPath method to adjust and get the correct element. Using %7 as there are 7 days. My cells are very simple - just a UILabel in it.

这一切都完美无缺.

问题来自 sizeForItemAtIndexPath.我希望单元格适合标签.由于只有 7 种实际大小变化,所以我在字典中预先缓存了 7 天的大小,并在 sizeForItemAtIndexPath 方法中返回正确的大小.

The issue comes with the sizeForItemAtIndexPath. I want the cells to fit the label. As there would only be 7 actual size variations, so I pre-cache the sizes of the 7 days in a dictionary and return the correct size in sizeForItemAtIndexPath method.

问题是(由于一个错误或 Apple 对 collectionview 的故意不良设计),sizeForItemAtIndexPath 在 collectionview 出现之前会调用每个 indexPath.因此,如果我想要循环 collectionView 逻辑并需要返回大数字(10000),它会为所有 10000 个索引调用 sizeForItemAtIndexPath.所以在 collectionView 出现之前有几秒钟的延迟.如果我注释掉 sizeForItemAtIndexPath,它会立即生效.所以这绝对是问题所在.我在 sizeForItemAtIndexPath 中放了一个 NSLog,它会在加载前记录所有 22222 次调用.

The problem is that (either due to a bug or intentional bad design by Apple of collectionview), the sizeForItemAtIndexPath gets calls for every indexPath before the collectionview appears. So if I want to have the circular collectionView logic and need to return the big number (10000), it's is calling sizeForItemAtIndexPath for all 10000 indexes. So there is a couple seconds lag until the collectionView appears. If I comment out the sizeForItemAtIndexPath, then it works instantly. So that's definitely the issue. I put a NSLog in the sizeForItemAtIndexPath and it logs all the 22222 calls before load.

我什至定义了setEstimatedItemSize,它仍然为所有索引调用sizeForItemAtIndexPath.

I have even defined the setEstimatedItemSize, it still calls sizeForItemAtIndexPath for all indexes.

我可以通过返回较小的数字 1000 来减少延迟,但是,这肯定是一个糟糕的设计或错误.

I can reduce the lag by returning a smaller number 1000 but still, this is a bad design or bug for sure.

TableView 没有这个错误——你可以定义一百万行,它只在实际需要时调用 heightForRow.所以我不确定为什么 collectionView 需要在显示之前为所有单元格调用它,特别是如果 setEstimatedItemSize 也已经定义了.

TableView doesn't have this bug - you can define a million rows and it only calls the heightForRow when it actually needs it. So I am not sure why collectionView needs to call it for all cells before showing, especially if setEstimatedItemSize is already defined too.

这个错误的另一个副作用是,如果我返回一个更大的值,collectionView 会抛出一个错误(50000 使它中断,22222 没问题).它打印值太大的错误:

Another side-effect of this bug is that the collectionView throws an error if I return a bigger value (50000 makes it break, 22222 is okay). It prints the error for too big values:

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 可以轻松处理巨大的值,因为它没有这个错误.

TableView can easily handle huge values because it doesn't have this bug.

我也试过禁用 prefetching 但没有效果.

I have also tried disabling prefetching but that had no effect.

大家怎么看?

相关代码:

#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];
}

很少有人提到为此使用滚动视图而不是集合视图.

Few people mentioned using scrollview instead of collectionview for this.

几乎在我为圆形滚动视图研究的所有地方,他们都建议为此目的使用集合视图,因为它更容易.我真正的需求有点复杂,这也需要我使用 collectionview.

Pretty much everywhere I researched for the circular scrollview, they recommended using a collectionview for this purpose as it's much easier. My real requirement is a bit more complex which requires me to use collectionview too.

Scrollview 要求您使用 scrollViewDidScroll 方法并每次更改内容偏移量.此外,它一次加载内存中的所有视图,因为它没有像 collectionview 那样重用现有单元格的优势.所以这是另一个内存命中.

Scrollview requires you to use the scrollViewDidScroll method and change the content offset each time. Plus it loads all views in memory at once as it doesn't have the advantage of reusing existing cells like collectionview does. So that's another memory hit.

7 个工作日是我使用的一个简单示例.如果有人想显示大量数据(100),这在滚动视图中将是一个非常糟糕的实现,而集合视图将呈现此错误.

The 7 weekdays is a simple example I used. If someone wants to show a lot of data (100), that would be a pretty bad implementation in scrollview and collectionview will present this bug.

推荐答案

这是预期的行为,它与 UICollectionView 没有太大关系 - 您使用的更多是 UICollectionViewFlowLayout.

That's expected behaviour and it's not so much related to UICollectionView - it is more UICollectionViewFlowLayout that you use.

由于每个单元格的大小不同,FlowLayout 将分别请求每个单元格的大小,以计算 CollectionView 的总大小 - 这是正确处理滚动、滚动条所必需的.

With each cell being different size, FlowLayout will request sizes for each cell separately, to calculate total size of CollectionView - that is required to properly handle scrolling, scrollbars.

UITableView 简单一点,因为它的布局更简单(只有高度很重要)——这就是为什么可以在那里使用估计尺寸.

UITableView it's a bit simpler, as it's layout is much simpler (only height matters) - that's why it's possible to use estimatedSize there.

整个Core Layout Process在这里有很好的解释:

The whole Core Layout Process is well explained here:

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

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

To overcome this problem I would recommend using your custom UICollectionViewLayout and move your caching logic and reusing sizes for cells inside CollectionViewLayout, not in your ViewController.

这篇关于sizeForItemAtIndexPath 在加载之前为所有索引调用 - 我很确定我要么遇到了 UICollectionView 错误,要么设计不佳的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆