developer tip

iOS 10 버그 : UICollectionView가 존재하지 않는 인덱스 경로가있는 셀에 대한 레이아웃 속성을 받았습니다.

copycodes 2020. 11. 22. 19:57
반응형

iOS 10 버그 : UICollectionView가 존재하지 않는 인덱스 경로가있는 셀에 대한 레이아웃 속성을 받았습니다.


iOS 10이 설치된 기기에서 내 앱을 실행하면이 오류가 발생합니다.

UICollectionView가 존재하지 않는 인덱스 경로가있는 셀에 대한 레이아웃 속성을 받았습니다.

iOS 8 및 9에서는 잘 작동합니다. 나는 조사 중이며 컬렉션 뷰 레이아웃을 무효화하는 것과 관련된 것을 발견했습니다. 성공하지 못한 채 그 솔루션을 구현하려고했기 때문에 직접적인 도움을 요청하고 싶습니다. 이것은 내 계층 뷰입니다.

->Table view 
    ->Each cell of table is a custom collection view [GitHub Repo][1]
        ->Each item of collection view has another collection view

내가 시도한 것은 삽입하는 것입니다

    [self.collectionView.collectionViewLayout invalidateLayout];

에서

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView

두 컬렉션보기의

또한 데이터를 다시로드하기 전에 레이아웃을 무효화하려고했지만 작동하지 않습니다.

누구든지 나에게 길을 안내해 줄 수 있습니까?


collectionView의 셀 수가 변경되었을 때 이런 일이 발생했습니다. reloadData를 호출 한 후 invalidateLayout이 누락 된 것으로 나타났습니다. 추가 한 후 더 이상 충돌이 발생하지 않았습니다. Apple은 iOS10의 collectionViews를 일부 수정했습니다. 이것이 이전 버전에서 동일한 문제가 발생하지 않는 이유라고 생각합니다.

내 최종 코드는 다음과 같습니다.

[self.collectionView reloadData];
[self.collectionView.collectionViewLayout invalidateLayout];

사용자 정의 레이아웃이있는 경우 prepare func를 재정의하는 동안 캐시 (UICollectionViewLayoutAttributes)를 지워야합니다.

    override func prepare() {
       super.prepare()
       cache.removeAll()
    }

두 컬렉션에 동일한 UICollectionViewFlowLayout을 추가했기 때문에 동일한 뷰에서 2 개의 collectionView로이 문제에 직면했습니다.

let layout = UICollectionViewFlowLayout()
collectionView1.collectionViewLayout = layout
collectionView2.collectionViewLayout = layout

물론 collectionView1 데이터가 변경되면 데이터를 다시로드 할 때 충돌이 발생합니다. 이것이 누군가를 도울 수 있다면.


이전 답변이 도움이되지만 자동 크기 조정 셀을 사용하면 크기가 올바르지 않습니다.

 UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
 layout.estimatedItemSize = CGSizeMake(60, 25);
 layout.itemSize = UICollectionViewFlowLayoutAutomaticSize;             
 self.collectionView.collectionViewLayout = layout;

나는 교체 하여이 문제를 해결합니다.

[self.collectionView reloadData];

...에

[self.collectionView reloadSections:indexSet];

@Katrin의 답변은 많은 도움이되었지만 한 줄을 더 추가하면 더 나은 결과를 얻을 수 있습니다.

collectionView.reloadData()
collectionView.collectionViewLayout.invalidateLayout()
collectionView.layoutSubviews() // <-- here it is :)

이 라인으로 크래시를 재현 할 수 있는지 아닌지는 지금 말할 수 없지만, 하나가있는 것 같네요 ... 그래도 은색 총알이 아니라 뭔가.


UICollectionView의 레이아웃을 재설정 하면 문제가 해결되었습니다.

let layout = UICollectionViewFlowLayout()
collectionView?.collectionViewLayout = layout

invalidateLayout제 경우에는 전화 가 충돌을 막지 못했습니다. (컬렉션 뷰의 항목 수가 증가하면 작동하지만 감소하면 작동하지 않습니다). 컨텍스트 : UITableViewCell 내부에 UICollectionView가 있고 테이블 셀을 다시 사용할 때 collectionView의 대리자를 재설정합니다. 캐시를 무효화하는 것이 아니라 델리게이트를 재설정 할 때마다 레이아웃 객체를 다시 생성 한 다음 reloadData ()를 호출하여 문제를 해결했습니다.

foo.dataSource = newDataSource
foo.delegate = newDelegate
foo.fixLayoutBug()
foo.reloadData()

func fixLayoutBug() {
    let oldLayout = collectionViewLayout as! UICollectionViewFlowLayout
    let newLayout = UICollectionViewFlowLayout()
    newLayout.estimatedItemSize = oldLayout.estimatedItemSize
    newLayout.footerReferenceSize = oldLayout.footerReferenceSize
    newLayout.headerReferenceSize = oldLayout.headerReferenceSize
    newLayout.itemSize = oldLayout.itemSize
    newLayout.minimumInteritemSpacing = oldLayout.minimumInteritemSpacing
    newLayout.minimumLineSpacing = oldLayout.minimumLineSpacing
    newLayout.scrollDirection = oldLayout.scrollDirection
    newLayout.sectionFootersPinToVisibleBounds = oldLayout.sectionFootersPinToVisibleBounds
    newLayout.sectionHeadersPinToVisibleBounds = oldLayout.sectionHeadersPinToVisibleBounds
    newLayout.sectionInset = oldLayout.sectionInset
    newLayout.sectionInsetReference = oldLayout.sectionInsetReference
    collectionViewLayout = newLayout
}

매번 데이터를 다시로드하는 것은 좋지 않습니다 (insertItems 및 deleteItems, 심지어 reloadSections를 사용해야 함). 하지만 ... 어떤 경우에는 유효하다고 말한 후 실제로 다음을 수행 할 수 있습니다.

collectionView.dataSource = nil

collectionView.delegate = nil

/*... All changes here. ... */

collectionView.dataSource = self

collectionView.delegate = self

collectionView.reloadData()

제 경우에는 NSlayoutConstraint 상수를 변경했습니다.

self.collectionViewContacts.collectionViewLayout.invalidateLayout()

            UIView.animate(withDuration: 0.3) {
                self.view.layoutIfNeeded()
            }


            self.collectionViewContacts.reloadData()

문제를 해결


RxDataSources사용하는 데 문제가 있었고 RxCollectionViewSectionedAnimatedDataSource두 가지 답변을 결합하여 해결했습니다. invalidateLayout()내 컬렉션을 반응 적으로 다시로드 해야합니다 .

    ...
    .asDriver(onErrorJustReturn: [])
    .do(onNext: { [weak self] _ in
        DispatchQueue.main.async {
            self?.collectionViewLayout.invalidateLayout()
            self?.collectionView.layoutSubviews()
        }
    }).drive(collectionView.rx.items(dataSource: collectionController.dataSource))
    .disposed(by: disposeBag)

레이아웃 방법 cache에서 배열 을 지우십시오 prepare(). 다음은 코드입니다.

override func prepare() {
    super.prepare()
    cache.removeAll()
    guard let collectionView = collectionView,
        collectionView.numberOfSections > 0,
        let delegate = delegate else {
        return
    }
    ...

스크롤 위치를 유지하고 충돌을 수정하려면 다음을 사용할 수 있습니다.

collectionView.reloadData()
let context = collectionView.collectionViewLayout.invalidationContext(forBoundsChange: collectionView.bounds)
context.contentOffsetAdjustment = CGPoint.zero
collectionView.collectionViewLayout.invalidateLayout(with: context)

애니메이션 제약 변경을 사용하여 컬렉션보기를 숨기는 동안 비슷한 문제에 직면했습니다. 내 솔루션은 기존 답변을 기반으로합니다.

self.filtersCollectionView.reloadData()
if isNeedToUpdateConstraint {
    filtersCollectionViewHeightLayoutConstraint.constant = updatedHeight
    UIView.animate(withDuration: 0.1, animations: {
        self.view.setNeedsLayout()
    }, completion: { completion in
        if completion {
            self.filtersCollectionView.collectionViewLayout.invalidateLayout()
        }
    })
}

I had this problem as well. In my case there was a custom UICollectionViewLayout applied to the collection view & the problem was that it had stale data for the number of cells that should be displayed. That's definitely something you can check on when you see UICollectionView received layout attributes for a cell with an index path that does not exist


I managed to solve this by recreating the collectionViewLayout before calling reloadData()

The issue was probably related to me having a separate instance for the dataSource (i.e. not the view controller holding the collectionView), and when swapping datasource, the crash could appear.


This happened to me as well, but it was because my UICollectionViewDataSource changed, and I didn't call -[UICollectionView reloadData]. In my case, I had the following data structure:

struct Bar { let name: String }
struct Foo { let name: String; let bars: [Bar] }

I had two UICollectionViews: one for Foos and one for Bars. In my -collectionView:didSelectItemAtIndexPath:, I had the following:

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
  if (collectionView == self.fooCollectionView) {
    self.selectedFoo = self.foos[indexPath.row];
    [self.barCollectionView reloadData];
    self.fooCollectionView.hidden = YES;
    self.barCollectionView.hidden = NO;
  } else if (collectionView == self.barCollectionView) {
    // do some stuff with the selected bar

    self.selectedFoo = nil;
    // This -reloadData call fixed my error. I thought I didn't
    // need it since my UICollectionView was hidden
    [self.barCollectionView reloadData];
    self.barCollectionView.hidden = YES;
    self.fooCollectionView.hidden = NO;
  }
}

Without the -reloadData call, I would see the crash when I rotated the device.


After spending two days of time, the below code solved the issue. Before loading a collectionview write the below code.

let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .horizontal
collecView.collectionViewLayout = flowLayout 
collecView.delegate = self
collecView.dataSource = self
collecView.reloadData()

Then the crash will be resolved, but you will find issue in collectionview cells, as the cells are compressed or not as per your design. Then just a line of code after scrolldirection line

flowLayout.itemSize = CGSize(width: 92, height: 100)

With the above line of code, you can adjust you collectionview cell layout.

I hope it will help someone.


In my case I was changing the constant of NSLayoutConstraint none of the above solution worked for me. thanks to @jeff ayan whose answer gave me the hint. I solved the problem with this code

override func prepareForReuse() {
    super.prepareForReuse()
    imagesCollectionHeight.constant = 100 // changing collectionview height constant
    imageContainerWidth.constant = 100  //changing width constant of some view
    self.layoutIfNeeded() // this line did the trick for me
}

Hope it helps someone


The following made it for me:

[self.collectionView reloadData];
[self.collectionView layoutIfNeeded];

This code seems to force collectionView to scroll to the first cell before reloading data, what seems to cause the error in my case.

참고URL : https://stackoverflow.com/questions/39867325/ios-10-bug-uicollectionview-received-layout-attributes-for-a-cell-with-an-index

반응형