2012-09-26 164 views
58

我M在UICollectionViewUICollectionView断言失败

断言失败收到此错误上执行insertItemsAtIndexPaths在:

-[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' 

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

回答

67

我将第一单元到一个集合视图时,就遇到了这个同样的问题。我固定,通过改变我的代码的问题,使我称之为UICollectionView

- (void)reloadData 

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

- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths 
将所有其他细胞时

有趣的是,我还删除了最后一个单元格时,有一个问题

- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths 

。我做了和以前一样的事情:当删除最后一个单元格时,请拨打reloadData

+14

这似乎是正确的答案。如果您插入第一个单元格,则会发生崩溃。这个问题有一个开放的雷达:http://openradar.appspot.com/12954582其中说,如果您使用“装饰视图”(节头或页脚),问题也只会发生。 – chris

+2

对第一项使用reloadData和对以下项目使用insertItemsAtIndexPaths的补充视图不适用于我。但是,如果我禁用补充视图,那么它的作品。如果我只使用reloadData,它可以工作。可悲的是我想用我的KVO代码使用-insertItemsAtIndexPaths。 – neoneye

+0

来扩展这个答案,如果我在'performBatchUpdates:'块之外和之前调用'reloadData',UI会根据需要进行更新并且崩溃消失。 – toblerpwn

1

检查你在UICollectionViewDataSource方法返回正确的元素数量:

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

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView 
+0

是的,它的返回刚好1.显然这个问题只发生在插入第一个项目。从那时起,这不是一个问题。但是,当我在第一个项目上尝试“reloadData”集合视图时,没有问题。 – jajo87

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]; 
+0

这似乎适用于我,我也用 –

+4

这部分的补充视图,这对我没有效用:( – haider

+1

为了得到这个工作,我不得不调整'numberOfSectionsInCollectionView的返回值:'如果你的集合是空的,返回0个部分,如果集合不是的话,返回1。如果你没有实现这个方法或返回0,你会得到'无效更新'错误,抱怨在插入之后部分的数量不正确。 – nrj

1

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

  1. 我子类UICollectionViewController和上initWithCollectionViewLayout:,被初始化我NSFetchedResultsController
  2. 有一个共同的类获取从NSURLConnection的结果,并通过进料解析JSON字符串(不同的线程)
  3. 环路和创建我NSManagedObjects,将它们添加到我的NSManagedObjectContext,其中有主线程的NSManagedObjectContext作为parentContext
  4. 保存我的上下文。
  5. 让我的NSFetchedResultsController拿起更改并排队。
  6. - (void)controllerDidChangeContent:,我会处理更改并将它们应用于我的UICollectionView

间歇性地,我会得到OP得到的错误,并找不到原因。

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

希望这会有所帮助!

1

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

我只能规避碰撞,仍然通过在部分包含这样的补充头部查看空的和无形的细胞维持动画:

  1. 制作- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section返回实际的电池`数+ 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:没有崩溃。
+1

我有这个问题没有任何补充意见。 – kelin

0

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

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

我的集合视图正在从两个数据源获取项目并更新它们导致此问题。我的解决方法是队列中的数据更新和集合视图重装在一起:

[[NSOperationQueue mainQueue] addOperationWithBlock:^{ 

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

       //Reload CollectionView 
       [weakSelf.collectionView reloadItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:0 inSection:0]]]; 
}]; 
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。

+1

我在这里找到了Blake的方法:https://gist.github.com/Lucien/4440c1cba83318e276bb,但由于错误(我认为),我无法使它工作。所以非常感谢花时间发布这个完整版本。它工作得很好! – PJC

+0

谢谢。不过,看起来'NSFetchedResultsChangeInsert'中的if子句应检查'* new * IndexPath.section'节中的项目数。 –

+0

像这样使用NSBlockOperation是很危险的,因为它不能保证在主线程中调用添加的块。有关详细信息,请参阅我的评论[此处](https://gist.github.com/Lucien/4440c1cba83318e276bb#comment-1184323)。 –

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

,实际工作的解决方法是返回的高度为0,如果在你的补充视图的索引路径的电池是不存在的(初始负荷,你已经删除的行等) 。看到这里我的答案:

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

4

这里是一个非劈,基于文档,这个问题的答案。在我的情况下,有一个条件,我会从collectionView:viewForSupplementaryElementOfKind:atIndexPath:返回有效或无效的补充视图。遇到崩溃后,我检查了文档,这里是他们说的:

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

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

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

这应该是正确的答案,它不是苹果的错误! – scorpiozj

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

对我来说,问题是我创建我NSIndexPath的方式。例如,要删除第三单元,而不是做:

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

我需要做的:

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

检查你在numberOfSectionsInCollectionView:

值我返回正确的值正在使用计算部分是零,因此0部分。这导致了异常。

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

    // section count is wrong! 

    return sectionCount; 
}