Ошибка утверждения UICollectionView


Я получаю эту ошибку при выполнении insertItemsAtIndexPaths in 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 59

14 ответов:

я столкнулся с этой самой проблемой при вставке первой ячейки в представление коллекции. Я исправил проблему, изменив свой код так, что я вызываю UICollectionView

- (void)reloadData

способ при вставке первой ячейки, но

- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths

при вставке всех других клеток.

интересно, у меня тоже была проблема С

- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths

при удалении последней ячейке. Я сделал то же самое, что и раньше: просто позвоните reloadData когда удаление последней ячейки.

вставка раздела#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];

я разместил работу вокруг этой проблемы здесь:https://gist.github.com/iwasrobbed/5528897

в частной категории в верхней части вашего .m file:

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

заслуга в этом подходе принадлежит Блейку Уоттерсу.

вот не-хак, основанный на документах ответ на проблему. В моем случае было условие, согласно которому я вернул бы действительный или нулевой дополнительный вид из collectionView:viewForSupplementaryElementOfKind:atIndexPath:. После столкновения с аварией, я проверил документы и вот что они говорят:

этот метод всегда должен возвращать допустимый объект представления. Если вы не хотите дополнительный вид в конкретном случае ваш объект макета должен не создавайте атрибуты для этого представления. Кроме того, вы можете прятаться представления путем установки скрытого свойства соответствующих атрибутов да или задайте для свойства alpha атрибутов значение 0. Скрывать виды верхнего и нижнего колонтитулов в компоновке потока можно также задать ширину и высота этих видов до 0.

есть и другие способы сделать это, но самый быстрый, кажется:

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

мой вид коллекции получал элементы из двух источников данных и обновление их вызвало эту проблему. Мой обходной путь состоял в том, чтобы поставить в очередь обновление данных и перезагрузить представление коллекции вместе:

[[NSOperationQueue mainQueue] addOperationWithBlock:^{

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

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

убедитесь, что вы возвращаете правильное количество элементов в UICollectionViewDataSource методы:

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

и

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView

я столкнулся с этой проблемой. Вот что случилось со мной:

  1. я подкласс UICollectionViewController и, на initWithCollectionViewLayout:, инициализировал мой NSFetchedResultsController.
  2. есть общий класс выборки результатов из NSURLConnection и проанализировать строку JSON (другой поток)
  3. цикл через канал и создать мой NSManagedObjects добавьте их к моему NSManagedObjectContext, который имеет основной поток NSManagedObjectContext как parentContext.
  4. сохранить мой контекст.
  5. мой NSFetchedResultsController забрать изменения и поставить их в очередь.
  6. On - (void)controllerDidChangeContent:, Я бы обработал изменения и применил их к моему UICollectionView.

периодически я получаю ошибку, которую получает OP, и не могу понять, почему.

чтобы исправить эту проблему, я переместил NSFetchedResultsController инициализации и performFetch мой - viewDidLoad метод и эта проблема теперь исчезла. Нет необходимости звонить [collectionView reloadData] или что-нибудь и все анимации работает исправно.

надеюсь, что это помогает!

похоже, что проблема возникает при вставке или перемещении ячейки в раздел, содержащий дополнительный вид верхнего или нижнего колонтитула (с 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: без сбоев.

вот решение для этой ошибки, которую я использовал в своих проектах, я думал, что опубликую здесь, Если кто-нибудь найдет это ценным.

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

в моем случае проблема заключалась в том, как я создавал свой NSIndexPath. Например, чтобы удалить 3-ю ячейку, вместо того, чтобы делать :

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

мне нужно :

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

проверьте, что вы возвращаете правильное значение в numberOfSectionsInCollectionView:

значение, которое я использовал для расчета секций, было равно нулю, таким образом 0 разделы. Это вызвало исключение.

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

    // section count is wrong!

    return sectionCount;
}

просто для записи, я столкнулся с такой же проблемой и для меня решение было удалить заголовок (отключить их .xib) и поскольку они больше не были нужны, этот метод был удален. После этого все кажется прекрасным.

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

Я сам столкнулся с этой проблемой. Все ответы здесь, казалось, представляли проблемы, за исключением Алекса л. ссылка на обновление, казалось, была ответом. Вот мое окончательное решение:

- (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, если ячейка в пути индекса вашего дополнительного представления не существует (начальная загрузка, вы удалили строку и т. д.). Смотрите мой ответ здесь:

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