Подтвердить что ты не робот

Ошибка UICollectionView Assertion

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

Я проверил, и мой источник данных содержит только один элемент. Любые идеи о том, почему это может произойти? Если вам нужна дополнительная информация, я могу определенно предоставить это.

4b9b3361

Ответ 1

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

- (void)reloadData

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

- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths

при вставке всех других ячеек.

Интересно, что у меня также была проблема с

- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths

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

Ответ 2

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

Ответ 3

Я опубликовал работу для этой проблемы здесь: 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];
    }
}

Кредит для этого подхода относится к Блейку Уоттеру.

Ответ 4

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

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

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

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

Ответ 5

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

[[NSOperationQueue mainQueue] addOperationWithBlock:^{

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

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

Ответ 6

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

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

и

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView

Ответ 7

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

  • Я подклассифицировал UICollectionViewController и на initWithCollectionViewLayout: инициализировал мой NSFetchedResultsController.
  • Получите общие результаты выборки класса из NSURLConnection и проанализируйте строку JSON (другой поток)
  • Прокрутите фид и создайте мой NSManagedObjects, добавьте их в мой NSManagedObjectContext, который имеет основной поток NSManagedObjectContext как parentContext.
  • Сохраните мой контекст.
  • Подведите мои NSFetchedResultsController к изменениям и оставьте их в очереди.
  • В - (void)controllerDidChangeContent: я обработал изменения и применил их к моему UICollectionView.

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

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

Надеюсь, это поможет!

Ответ 8

Кажется, что проблема возникает, когда вы вставляете или перемещаете ячейку в раздел, который содержит дополнительный заголовок или нижний колонтитул (с UICollectionViewFlowLayout или макет, полученный из этого), и в секции есть количество 0 ячеек до вставка/перемещение.

Я мог только обойти крушение и по-прежнему поддерживать анимацию, имея пустую и невидимую ячейку в секции, содержащей дополнительное представление заголовка следующим образом:

  • Сделайте - (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section, чтобы вернуть фактический номер ячейки `count + 1 для этого раздела, где находится заголовок.
  • В - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath return

    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"];
    
  • Используйте moveItemAtIndexPath: или insertItemsAtIndexPaths: без сбоев.

Ответ 9

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

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

Ответ 10

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

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

Мне нужно было сделать:

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

Ответ 11

Убедитесь, что вы вернули правильное значение в numberOfSectionsInCollectionView:

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

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

    // section count is wrong!

    return sectionCount;
}

Ответ 12

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

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

Ответ 13

Я сам попал в эту проблему. Все ответы здесь представляли проблемы, за исключением 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]]];
    }];
}

Ответ 14

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

fooobar.com/info/94395/...