web-dev-qa-db-ja.com

UICollectionViewアサーションエラー

insertItemsAtIndexPathsUICollectionViewを実行するとこのエラーが発生します

アサーションエラー:

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

チェックしましたが、データソースに含まれる要素は1つだけです。これが起こる理由についての洞察はありますか?さらに情報が必要な場合は、間違いなく提供できます。

59
jajo87

最初のセルをコレクションビューに挿入するときに、これとまったく同じ問題に遭遇しました。 UICollectionViewを呼び出すようにコードを変更することで問題を修正しました

- (void)reloadData

最初のセルを挿入するときのメソッドですが、

- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths

他のすべてのセルを挿入するとき。

興味深いことに、私も問題がありました

- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths

最後のセルを削除するとき。前と同じことをしました。最後のセルを削除するときにreloadDataを呼び出すだけです。

68
Jay Slupesky

セルを挿入する直前にセクション#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];
11
neoneye

この問題の回避策をここに投稿しました: 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にあります。

5
iwasrobbed

ここに、問題に対する非ハック、ドキュメントベースの回答があります。私の場合、collectionView:viewForSupplementaryElementOfKind:atIndexPath:から有効またはゼロの補足ビューを返す条件がありました。クラッシュに遭遇した後、私はドキュメントをチェックしましたが、ここに彼らの言うことがあります:

このメソッドは、常に有効なビューオブジェクトを返す必要があります。特定のケースで補足ビューが必要ない場合は、レイアウトオブジェクトでそのビューの属性を作成しないでください。または、対応する属性のhiddenプロパティをYESに設定するか、属性のalphaプロパティを0に設定してビューを非表示にできます。フローレイアウトでヘッダービューとフッタービューを非表示にするには、これらのビューの幅と高さも設定できます0に。

これを行う方法は他にもありますが、最も簡単な方法は次のとおりです。

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

私のコレクションビューは2つのデータソースからアイテムを取得し、それらを更新するとこの問題が発生しました。私の回避策は、データ更新とコレクションビューのリロードを一緒にキューに入れることでした。

[[NSOperationQueue mainQueue] addOperationWithBlock:^{

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

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

プロジェクトで使用してきたこのバグの解決策を以下に示します。貴重なものが見つかった場合に備えて、ここに投稿すると思いました。

@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
gdavis

私の場合、問題はNSIndexPathの作成方法でした。たとえば、3番目のセルを削除するには、次のようにします。

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

私がする必要がありました:

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

numberOfSectionsInCollectionView:で正しい値を返していることを確認してください

セクションの計算に使用していた値はnilでした。したがって、0セクションです。これにより例外が発生しました。

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

    // section count is wrong!

    return sectionCount;
}
1
pkamb

私もこの問題に遭遇しました。私に起こったことは次のとおりです。

  1. UICollectionViewControllerをサブクラス化し、_initWithCollectionViewLayout:_でNSFetchedResultsControllerを初期化していました。
  2. 共有クラスがNSURLConnectionから結果を取得し、JSON文字列を解析する(異なるスレッド)
  3. フィードをループしてNSManagedObjectsを作成し、メインスレッドのNSManagedObjectContextNSManagedObjectContextとして持つparentContextに追加します。
  4. コンテキストを保存します。
  5. NSFetchedResultsControllerに変更を選択させ、それらをキューに入れます。
  6. - (void)controllerDidChangeContent:で、変更を処理してUICollectionViewに適用します。

断続的に、OPが取得しているエラーが表示され、理由がわかりませんでした。

この問題を修正するために、NSFetchedResultsController初期化とperformFetchを_- viewDidLoad_メソッドに移動しましたが、この問題はなくなりました。 _[collectionView reloadData]_または何かを呼び出す必要はなく、すべてのアニメーションは正常に動作しています。

お役に立てれば!

1
Simon Germain

問題は、補助ヘッダーまたはフッタービューを含むセクション(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: クラッシュすることなく。
1

UICollectionViewDataSourceメソッドで正しい要素数を返していることを確認してください。

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

そして

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
1
Morrowless

記録のためだけに、私は同じ問題にぶつかりました。私にとって解決策はヘッダーを削除することで(.xibでヘッダーを無効にします)、ヘッダーが不要になったため、このメソッドは削除されました。その後、すべてがうまくいくようです。

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

私は自分でこの問題にぶつかりました。ここでの答えはすべて、アレックスLを除いて問題を提示しているように見えました。更新を参照することは答えのように見えました。これが私の最終的な解決策です。

- (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
Owen Godfrey

実際に機能する回避策は、補助ビューのインデックスパスのセルが存在しない場合(初期ロード、行の削除など)、高さ0を返すことです。ここで私の答えを参照してください:

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

0
deepseadiving