2012-09-26 161 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; 
}