UICollectionView 使用自定义布局抛出未捕获的异常

Posted

技术标签:

【中文标题】UICollectionView 使用自定义布局抛出未捕获的异常【英文标题】:UICollectionView throws uncaught exception using custom layout 【发布时间】:2015-08-20 13:03:06 【问题描述】:

我正在使用带有自定义布局的集合视图,我第一次从 API 收到的调用 web 服务返回 20 个对象,第二次它将返回 1 个对象,而重新加载数据应用程序会引发以下错误。

由于未捕获的异常“NSInternalInconsistencyException”而终止应用程序,原因:UICollectionView 接收到具有索引的单元格的布局属性 不存在的路径:length = 2, path = 0 - 0

创建新布局的代码

-(void)doNewLayout 

 id<UICollectionViewDelegateJSPintLayout> delegate = (id<UICollectionViewDelegateJSPintLayout>)self.collectionView.delegate;

// get column width from delegate.  If the method isn't implemented fall back to our property
    NSUInteger columnWidth = self.columnWidth;
    if(delegate && [delegate respondsToSelector:@selector(columnWidthForCollectionView:layout:)])
    
        columnWidth = [delegate columnWidthForCollectionView:self.collectionView
                                                      layout:self];
    

    // find out how many cells there are
    NSUInteger cellCount = [self.collectionView numberOfItemsInSection:0];

    // get max number of columns from the delegate.  If the method isn't implemented, fall back to our property
    NSUInteger maximumNumberOfColumns = self.numberOfColumns;
    if(delegate && [delegate respondsToSelector:@selector(maximumNumberOfColumnsForCollectionView:layout:)])
        maximumNumberOfColumns = [delegate maximumNumberOfColumnsForCollectionView:self.collectionView layout:self];
    

    // build an array of all the cell heights.
    NSMutableArray* cellHeights = [NSMutableArray arrayWithCapacity:cellCount];
    for(NSUInteger cellIndex = 0; cellIndex < cellCount; ++cellIndex)
    
        CGFloat itemHeight = self.itemHeight;  // set default item size, then optionally override it
        if(delegate && [delegate respondsToSelector:@selector(collectionView:layout:heightForItemAtIndexPath:)])
        
            itemHeight = [delegate collectionView:self.collectionView
                                           layout:self
                         heightForItemAtIndexPath:[NSIndexPath indexPathForItem:cellIndex
                                                                      inSection:0]];
        

        cellHeights[cellIndex] = @(itemHeight);
    

    // now build the array of layout attributes
    self.pendingLayoutAttributes = [NSMutableArray arrayWithCapacity:cellCount];

    // will need an array of column heights
    CGFloat* columnHeights = calloc(maximumNumberOfColumns,sizeof(CGFloat));  // calloc() initializes to zero.
    CGFloat contentHeight = 0.0;
    CGFloat contentWidth = 0.0;
    for(NSUInteger cellIndex = 0; cellIndex < cellCount; ++cellIndex)
    
        CGFloat itemHeight = [cellHeights[cellIndex] floatValue];

        // find shortest column
        NSUInteger useColumn = 0;
        CGFloat shortestHeight = DBL_MAX;
        for(NSUInteger col = 0; col < maximumNumberOfColumns; ++col)
        
            if(columnHeights[col] < shortestHeight)
            
                useColumn = col;
                shortestHeight = columnHeights[col];
            
        

        NSIndexPath* indexPath = [NSIndexPath indexPathForItem:cellIndex
                                                     inSection:0];
        UICollectionViewLayoutAttributes* layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

        layoutAttributes.size = CGSizeMake(columnWidth,itemHeight);
        layoutAttributes.center = CGPointMake((useColumn * (columnWidth + self.interitemSpacing)) + (columnWidth / 2.0),columnHeights[useColumn] + (itemHeight / 2.0));
        self.pendingLayoutAttributes[cellIndex] = layoutAttributes;
        columnHeights[useColumn] += itemHeight;
        if(columnHeights[useColumn] > contentHeight)
            contentHeight = columnHeights[useColumn];
        CGFloat rightEdge = (useColumn * (columnWidth + self.interitemSpacing)) + columnWidth;
        if(rightEdge > contentWidth)
            contentWidth = rightEdge;
        columnHeights[useColumn] += self.lineSpacing;
    
    self.contentSize = CGSizeMake(contentWidth,contentHeight+100);

    free(columnHeights);

任何快速解决方案将不胜感激。谢谢

【问题讨论】:

您可能需要显示更多代码,您如何告诉集合视图应该绘制多少个单元格? 添加您的自定义布局代码,似乎您没有为所有 inexPaths 创建 layoutAttributes,当您将项目插入到集合视图但不更改自定义布局时会发生这种情况 @C_X 添加了新布局创建的代码 sn-p。 所以这将计算一次或每一个新项目...你能在调用 collectionView.reloadData() 的地方添加代码吗? 哪一行导致了错误?添加异常断点。它还会显示当时的调用堆栈。 【参考方案1】:

有几件事需要考虑,因为您是自定义布局,所以您需要为每个 indexPath 创建 layoutAttribute。在您的情况下,您的数据源数组计数和offerModel.arrayOffersself.pendingLayoutAttributes 应该是相同的,如果不是,那么如果offerModel.arrayOffers拥有更多项目然后self.pendingLayoutAttributes,则可能是问题及其可压碎的问题。

如果您正在异步加载数据,请确保在 arraysOffers 中添加行时还在 customLayout pendLayoutAttributes 中添加 layoutAttributes,我认为您目前没有这样做,通过添加方法并提供新的 @987654326 来做到这一点@ 到创建 layoutAttributes 的那个。

我经常这样

- (void)insertItems:(NSArray*)items

    NSInteger startIndex = <start index for new item>;
    [self.items addObjectsFromArray:items];
    [self calculateLayoutForItems:items startIndex:startIndex];

这个方法会计算layoutAttributes

- (void)calculateLayoutForItems:(NSArray*)items startIndex:(NSInteger)startIndex

    // collection view and loop over them as well and nest indexPath creation

    NSInteger section = 0;
    NSInteger endIndex = self.items.count;

    // Lets create indexPaths for provided items and get there frames
    for (NSInteger index = startIndex ;index < endIndex; index++)
    
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:section];
        CGRect frame = [self frameForItemAtIndexPath:indexPath];// call your method for frame at indexPath

        UICollectionViewLayoutAttributes *itemAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
        itemAttributes.frame = frame;
        self.pendingLayoutAttributes[indexPath] = itemAttributes; // you can use your index
    

现在,当您在数据源中获得更多项目时,请在 customLayoutObject [self.customLayout insertItems:newItemsArray]; 上调用此方法

此外,如果您已经存储了属性,那么它值得在自定义布局中覆盖 invalidate 方法以将所有属性重置为初始状态,然后 你可以在 reloadData 方法之前 invalidate customLayout ,然后集合视图将强制它再次计算布局。

【讨论】:

感谢您的澄清。我添加了以下语句来重新加载数据,它似乎对我不起作用,并导致与我发布的相同问题。 [self.offerCollectionView.collectionViewLayout 无效布局]; [offerCollectionView reloadSections:[NSIndexSet indexSetWithIndex:0]]; [offerCollectionView reloadData]; 我已经用示例代码更新了我的答案,您可以如何在 customLyaout 中添加更多项目 您也可以使用'-performBatchUpdates:',这将使布局无效并自动重新加载数据。您应该在该块中插入/删除您的对象。 是的,可以添加项目然后更新,但问题是自定义布局中没有新 indexPaths 的布局属性可能是他正在使用将存储特定值的属性...尝试覆盖无效布局并重置一切

以上是关于UICollectionView 使用自定义布局抛出未捕获的异常的主要内容,如果未能解决你的问题,请参考以下文章

具有自定义布局的 UICollectionView 变为空白

Swift 中的 UICollectionView 自定义布局

使用 Swift 3 在 UICollectionView 上自定义布局

UICollectionView 中的自定义布局

UICollectionView:使用动画和自定义布局调整单元格大小

iOS UICollectionView自定义流水布局(一)