Program Tip

UICollectionView 어설 션 실패

programtip 2020. 12. 8. 19:52
반응형

UICollectionView 어설 션 실패


수행 할 때이 오류가 발생합니다 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'

확인했고 데이터 소스에 요소가 하나만 포함되어 있습니다. 왜 이런 일이 일어날 수 있는지에 대한 통찰력이 있습니까? 더 많은 정보가 필요하면 확실히 제공 할 수 있습니다.


컬렉션 뷰에 첫 번째 셀을 삽입 할 때 이와 동일한 문제가 발생했습니다. 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파일 상단의 비공개 카테고리에서 :

@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에게 있습니다.


다음은 문제에 대한 해킹이 아닌 문서 기반 답변입니다. 제 경우에는에서 유효한 또는 nil 보충보기를 반환하는 조건이있었습니다 collectionView:viewForSupplementaryElementOfKind:atIndexPath:. 충돌이 발생한 후 문서를 확인했으며 다음과 같이 말합니다.

이 메서드는 항상 유효한 뷰 개체를 반환해야합니다. 특정 경우에 추가보기를 원하지 않는 경우 레이아웃 개체는 해당보기에 대한 속성을 생성하지 않아야합니다. 또는 해당 속성의 숨김 속성을 YES로 설정하거나 속성의 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이 메인 스레드의있는 NSManagedObjectContextA와 parentContext.
  4. 내 맥락을 저장하십시오.
  5. 내 한 NSFetchedResultsController변경 사항을 선택하고 그들을 대기.
  6. - (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];
    }
    

    ... 그 위치에 대한 빈 셀. viewDidLoadUICollectionView를 초기화 할 때마다 셀 재사용을 등록해야합니다 .

    [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. 예를 들어, 다음을 수행하는 대신 세 번째 셀을 삭제하려면 다음을 수행하십시오.

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

기록을 위해 동일한 문제가 발생했으며 해결책은 헤더를 제거하는 것이었고 (.xib에서 비활성화) 더 이상 필요하지 않았기 때문에이 방법을 제거했습니다. 그 후 모든 것이 잘 보입니다.

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

이 문제를 직접 해결했습니다. 여기에있는 모든 대답은 Alex 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]]];
    }];
}

The workaround that actually works is to return a height of 0 if the cell at your supplementary view's index path is not there (initial load, you've deleted the row, etc). See my answer here:

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

참고URL : https://stackoverflow.com/questions/12611292/uicollectionview-assertion-failure

반응형