UICollectionView断言失败

时间:2012-09-26 22:16:57

标签: objective-c ios6 uicollectionview

我在insertItemsAtIndexPaths

中执行UICollectionView时遇到此错误

断言失败:

-[UICollectionViewData indexPathForItemAtGlobalIndex:], 
/SourceCache/UIKit/UIKit-2372/UICollectionViewData.m:442
2012-09-26 18:12:34.432  
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', 
reason: 'request for index path for global index 805306367 
when there are only 1 items in the collection view'

我已经检查过,我的数据源只包含一个元素。 有关为何会发生这种情况的任何见解?如果需要更多信息,我绝对可以提供。

14 个答案:

答案 0 :(得分:68)

将第一个单元格插入集合视图时遇到了同样的问题。我通过更改代码来修复问题,以便调用UICollectionView

- (void)reloadData

插入第一个单元格时的方法,但是

- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths

插入所有其他单元格时。

有趣的是,我也遇到了问题

- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths

删除最后一个单元格时。我做了和以前一样的事情:只需在删除最后一个单元格时调用reloadData

答案 1 :(得分:11)

在插入单元格之前插入部分#0似乎使UICollectionView感到高兴。

NSArray *indexPaths = /* indexPaths of the cells to be inserted */
NSUInteger countBeforeInsert = _cells.count;
dispatch_block_t updates = ^{
    if (countBeforeInsert < 1) {
        [self.collectionView insertSections:[NSIndexSet indexSetWithIndex:0]];
    }
    [self.collectionView insertItemsAtIndexPaths:indexPaths];
};
[self.collectionView performBatchUpdates:updates completion:nil];

答案 2 :(得分:5)

我已在此处发布了解决此问题的方法:https://gist.github.com/iwasrobbed/5528897

.m文件顶部的私人类别中:

@interface MyViewController ()
{
    BOOL shouldReloadCollectionView;
    NSBlockOperation *blockOperation;
}
@end

然后您的委托回调将是:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    shouldReloadCollectionView = NO;
    blockOperation = [NSBlockOperation new];
}

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
    __weak UICollectionView *collectionView = self.collectionView;
    switch (type) {
        case NSFetchedResultsChangeInsert: {
            [blockOperation addExecutionBlock:^{
                [collectionView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]];
            }];
            break;
        }

        case NSFetchedResultsChangeDelete: {
            [blockOperation addExecutionBlock:^{
                [collectionView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]];
            }];
            break;
        }

        case NSFetchedResultsChangeUpdate: {
            [blockOperation addExecutionBlock:^{
                [collectionView reloadSections:[NSIndexSet indexSetWithIndex:sectionIndex]];
            }];
            break;
        }

        default:
            break;
    }
}

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
    __weak UICollectionView *collectionView = self.collectionView;
    switch (type) {
        case NSFetchedResultsChangeInsert: {
            if ([self.collectionView numberOfSections] > 0) {
                if ([self.collectionView numberOfItemsInSection:indexPath.section] == 0) {
                    shouldReloadCollectionView = YES;
                } else {
                    [blockOperation addExecutionBlock:^{
                        [collectionView insertItemsAtIndexPaths:@[newIndexPath]];
                    }];
                }
            } else {
                shouldReloadCollectionView = YES;
            }
            break;
        }

        case NSFetchedResultsChangeDelete: {
            if ([self.collectionView numberOfItemsInSection:indexPath.section] == 1) {
                shouldReloadCollectionView = YES;
            } else {
                [blockOperation addExecutionBlock:^{
                    [collectionView deleteItemsAtIndexPaths:@[indexPath]];
                }];
            }
            break;
        }

        case NSFetchedResultsChangeUpdate: {
            [blockOperation addExecutionBlock:^{
                [collectionView reloadItemsAtIndexPaths:@[indexPath]];
            }];
            break;
        }

        case NSFetchedResultsChangeMove: {
            [blockOperation addExecutionBlock:^{
                [collectionView moveItemAtIndexPath:indexPath toIndexPath:newIndexPath];
            }];
            break;
        }

        default:
            break;
    }
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    // Checks if we should reload the collection view to fix a bug @ http://openradar.appspot.com/12954582
    if (shouldReloadCollectionView) {
        [self.collectionView reloadData];
    } else {
        [self.collectionView performBatchUpdates:^{
            [blockOperation start];
        } completion:nil];
    }
}

这种方法归功于Blake Watters。

答案 3 :(得分:4)

这是针对该问题的非黑客,基于文档的答案。在我的情况下,有一个条件,根据该条件,我将从collectionView:viewForSupplementaryElementOfKind:atIndexPath:返回有效或零补充视图。遇到崩溃后,我检查了文档,这就是他们所说的:

  

此方法必须始终返回有效的视图对象。如果你不想要   在特定情况下的补充视图,您的布局对象应该   不为该视图创建属性。或者,你可以隐藏   通过设置相应属性的隐藏属性来查看视图   为YES或将属性的alpha属性设置为0.隐藏   在流布局中的页眉和页脚视图中,您还可以设置宽度   和那些视图的高度为0。

还有其他方法可以做到这一点,但最快的似乎是:

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewFlowLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {
    return <condition> ? collectionViewLayout.headerReferenceSize : CGSizeZero;
}

答案 4 :(得分:3)

我的收藏视图是从两个数据源获取项目并更新它们导致此问题。我的解决方法是将数据更新和集合视图重新加载到一起排队:

[[NSOperationQueue mainQueue] addOperationWithBlock:^{

                //Update Data Array
                weakSelf.dataProfile = [results lastObject]; 

                //Reload CollectionView
                [weakSelf.collectionView reloadItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:0 inSection:0]]];
 }];

答案 5 :(得分:1)

检查您是否在UICollectionViewDataSource方法中返回了正确数量的元素:

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView

答案 6 :(得分:1)

我也遇到了这个问题。这就是发生在我身上的事情:

  1. 我将UICollectionViewController子类化,并在initWithCollectionViewLayout:上初始化我的NSFetchedResultsController
  2. 从NSURLConnection获取共享类获取结果并解析JSON字符串(不同的线程)
  3. 遍历Feed并创建我的NSManagedObjects,将其添加到我的NSManagedObjectContext,其主线程NSManagedObjectContextparentContext
  4. 保存我的上下文。
  5. 让我的NSFetchedResultsController接收更改并排队。
  6. - (void)controllerDidChangeContent:,我会处理更改并将其应用到我的UICollectionView
  7. 间歇性地,我会得到OP正在犯的错误,并且无法弄清楚原因。

    要解决此问题,我已将NSFetchedResultsController初始化和performFetch移至我的- viewDidLoad方法,此问题现已消失。无需拨打[collectionView reloadData]或任何内容,所有动画都能正常运行。

    希望这有帮助!

答案 7 :(得分:1)

当您将单元格插入或移动到包含补充页眉或页脚视图的部分(使用UICollectionViewFlowLayout或从中派生的布局)并且该部分的计数为0时,似乎会出现问题插入/移动前的细胞。

我只能通过在包含补充标题视图的部分中包含一个空的不可见单元格来绕过崩溃并仍然保持动画:

  1. - (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section为标题视图所在的部分返回实际单元格`count + 1。
  2. - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath返回

    if ((indexPath.section == YOUR_SECTION_WITH_THE_HEADER_VIEW) && (indexPath.item == [self collectionView:collectionView numberOfItemsInSection:indexPath.section] - 1)) {
            cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"EmptyCell" forIndexPath:indexPath];
    }
    

    ......该位置的空单元格。请记住在viewDidLoad或初始化UICollectionView的任何地方注册单元重用:

    [self.collectionView registerClass:[UICollectionReusableView class] forCellWithReuseIdentifier:@"EmptyCell"];
    
  3. 使用moveItemAtIndexPath:insertItemsAtIndexPaths:而不会崩溃。

答案 8 :(得分:1)

这是我在我的项目中使用的这个错误的解决方案我认为我会在这里发布,以防任何有价值的东西。

@interface FetchedResultsViewController ()

@property (nonatomic) NSMutableIndexSet *sectionsToAdd;
@property (nonatomic) NSMutableIndexSet *sectionsToDelete;

@property (nonatomic) NSMutableArray *indexPathsToAdd;
@property (nonatomic) NSMutableArray *indexPathsToDelete;
@property (nonatomic) NSMutableArray *indexPathsToUpdate;

@end
#pragma mark - NSFetchedResultsControllerDelegate


- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    [self resetFetchedResultControllerChanges];
}


- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
    switch(type)
    {
        case NSFetchedResultsChangeInsert:
            [self.sectionsToAdd addIndex:sectionIndex];
            break;

        case NSFetchedResultsChangeDelete:
            [self.sectionsToDelete addIndex:sectionIndex];
            break;
    }
}


- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{
    switch(type)
    {
        case NSFetchedResultsChangeInsert:
            [self.indexPathsToAdd addObject:newIndexPath];
            break;

        case NSFetchedResultsChangeDelete:
            [self.indexPathsToDelete addObject:indexPath];
            break;

        case NSFetchedResultsChangeUpdate:
            [self.indexPathsToUpdate addObject:indexPath];
            break;

        case NSFetchedResultsChangeMove:
            [self.indexPathsToAdd addObject:newIndexPath];
            [self.indexPathsToDelete addObject:indexPath];
            break;
    }
}


- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    if (self.sectionsToAdd.count > 0 || self.sectionsToDelete.count > 0 || self.indexPathsToAdd.count > 0 || self.indexPathsToDelete > 0 || self.indexPathsToUpdate > 0)
    {
        if ([self shouldReloadCollectionViewFromChangedContent])
        {
            [self.collectionView reloadData];

            [self resetFetchedResultControllerChanges];
        }
        else
        {
            [self.collectionView performBatchUpdates:^{

                if (self.sectionsToAdd.count > 0)
                {
                    [self.collectionView insertSections:self.sectionsToAdd];
                }

                if (self.sectionsToDelete.count > 0)
                {
                    [self.collectionView deleteSections:self.sectionsToDelete];
                }

                if (self.indexPathsToAdd.count > 0)
                {
                    [self.collectionView insertItemsAtIndexPaths:self.indexPathsToAdd];
                }

                if (self.indexPathsToDelete.count > 0)
                {
                    [self.collectionView deleteItemsAtIndexPaths:self.indexPathsToDelete];
                }

                for (NSIndexPath *indexPath in self.indexPathsToUpdate)
                {
                    [self configureCell:[self.collectionView cellForItemAtIndexPath:indexPath]
                            atIndexPath:indexPath];
                }

            } completion:^(BOOL finished) {
                [self resetFetchedResultControllerChanges];
            }];
        }
    }
}

// This is to prevent a bug in UICollectionView from occurring.
// The bug presents itself when inserting the first object or deleting the last object in a collection view.
// http://stackoverflow.com/questions/12611292/uicollectionview-assertion-failure
// This code should be removed once the bug has been fixed, it is tracked in OpenRadar
// http://openradar.appspot.com/12954582
- (BOOL)shouldReloadCollectionViewFromChangedContent
{
    NSInteger totalNumberOfIndexPaths = 0;
    for (NSInteger i = 0; i < self.collectionView.numberOfSections; i++)
    {
        totalNumberOfIndexPaths += [self.collectionView numberOfItemsInSection:i];
    }

    NSInteger numberOfItemsAfterUpdates = totalNumberOfIndexPaths;
    numberOfItemsAfterUpdates += self.indexPathsToAdd.count;
    numberOfItemsAfterUpdates -= self.indexPathsToDelete.count;

    BOOL shouldReload = NO;
    if (numberOfItemsAfterUpdates == 0 && totalNumberOfIndexPaths == 1)
    {
        shouldReload = YES;
    }

    if (numberOfItemsAfterUpdates == 1 && totalNumberOfIndexPaths == 0)
    {
        shouldReload = YES;
    }

    return shouldReload;
}

- (void)resetFetchedResultControllerChanges
{
    [self.sectionsToAdd removeAllIndexes];
    [self.sectionsToDelete removeAllIndexes];
    [self.indexPathsToAdd removeAllObjects];
    [self.indexPathsToDelete removeAllObjects];
    [self.indexPathsToUpdate removeAllObjects];
}

答案 9 :(得分:1)

就我而言,问题在于我创建NSIndexPath的方式。例如,要删除第3个单元格,而不是执行:

NSIndexPath* indexPath = [NSIndexPath indexPathWithIndex:2];
[_collectionView deleteItemsAtIndexPaths:@[indexPath]];

我需要这样做:

NSIndexPath* indexPath = [NSIndexPath indexPathForItem:2 inSection:0];
[_collectionView deleteItemsAtIndexPaths:@[indexPath]];

答案 10 :(得分:1)

检查您是否在numberOfSectionsInCollectionView:

中返回了正确的值

我用来计算部分的值是零,因此0部分。这引起了例外。

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    NSInteger sectionCount = self.objectThatShouldHaveAValueButIsActuallyNil.sectionCount;

    // section count is wrong!

    return sectionCount;
}

答案 11 :(得分:0)

仅仅为了记录,我遇到了同样的问题,对我来说解决方案是删除标题(在.xib中禁用它们),因为它们不再需要删除此方法。之后一切都很好。

- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementary
ElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath

答案 12 :(得分:0)

我自己也遇到了这个问题。除了Alex L's之外,这里的所有答案似乎都存在问题。引用更新似乎是回答。这是我的最终解决方案:

- (void)addItem:(id)item {
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        if (!_data) {
            _data = [NSMutableArray arrayWithObject:item];
        } else {
            [_data addObject:item];
        }
        [_collectionView insertItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:_data.count-1 inSection:0]]];
    }];
}

答案 13 :(得分:0)

实际工作的解决方法是,如果补充视图的索引路径中的单元格不存在(初始加载,您已删除该行等),则返回高度0。请在此处查看我的回答:

https://stackoverflow.com/a/18411860/917104