CollectionView Diffable Datasource apply()에서 supplementary header register() exception 오류 해결

Doyeong Kim·2023년 8월 4일
0

Swift

목록 보기
4/9

각 section 별로 header가 있을 수도 없을 수도 있는 CollectionView Compositional Layout 을 구현하고 diffable datasource에서 apply()를 해줄 때 계속 아래와 같은 exception 오류가 뜸..

1. 첫번째 오류:

libc++abi: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: self.supplementaryViewProvider || (self.supplementaryReuseIdentifierProvider && self.supplementaryViewConfigurationHandler)'
terminating with uncaught exception of type NSException

로그를 대충 읽어보니 supplementaryViewProvider와 supplementaryReuseIdentifierProvider가 만족되지 않는다네.
아래 코드처럼 collectionView에 헤더뷰 register()를 해줬는데 dequeueReusableSupplementaryView 도 같이 해줘야하나보다.

내 코드 >

	// 1. Configure collectionView
	private lazy var collectionView = UICollectionView(frame: .zero, collectionViewLayout: createLayout()).then {
        $0.register(TopBannerCollectionViewCell.self, forCellWithReuseIdentifier: TopBannerCollectionViewCell.cellIdentifier)
        $0.register(HomeCategoryCollectionViewCell.self, forCellWithReuseIdentifier: HomeCategoryCollectionViewCell.identifier)
        $0.register(CommunityMainCollectionViewCell.self, forCellWithReuseIdentifier: CommunityMainCollectionViewCell.identifier)
        
        // Header register
        $0.register(HomeTitleHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: HomeTitleHeaderView.identifier)
        $0.backgroundColor = .clear
    }
    
    // 2. configure header
    viewModel.datasource.supplementaryViewProvider = { [weak self] view, kind, index in
    	return self?.collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: HomeTitleHeaderView.identifier, for: index)
    }

기존에는 UICollectionViewDelegate의 "collectionView(:viewForSupplementaryElementOfKind:at:)" 함수 내에서 dequeueReusableSupplementaryView를 작성했다면

Diffable Datasource를 사용할 땐, "supplementaryViewProvider" 로 구현할 수 있음.

저러고 다시 앱을 돌려보니 이번엔 또 다른 오류...

libc++abi: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'the view returned from -collectionView:viewForSupplementaryElementOfKind:atIndexPath: does not match the element kind it is being used for. When asked for a view of element kind 'HomeTitleHeaderView' the data source dequeued a view registered for the element kind 'UICollectionElementKindSectionHeader'.'
terminating with uncaught exception of type NSException

이번엔 또 읽어보니 element kind가 매칭이 안된다..? 뭘까..
내가 알고있던 kind는 header인지 footer인지 구분하는 거고 헤더면 "UICollectionView.elementKindSectionHeader" 이거 쓰면 되는거 아니였냐구..!

내 코드 >

	// collectionView register header
    collectionView.register(CommunityMainCollectionViewCell.self, forCellWithReuseIdentifier: CommunityMainCollectionViewCell.identifier)
    
    // header reusable view dequeue
	collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: HomeTitleHeaderView.identifier, for: index)

흠,, 잘 넣어준 것 같은데 다시 잘 찾아보니...

이유를 찾았다.. 맞아 코드는 거짓말을 안하지 ㅠㅠㅠ 항상 사람이 문제야....

문제된 코드 >

	// item, group 관련 코드 생략
    
    let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(56))
    let headerElement = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize,
                                                                    elementKind: HomeTitleHeaderView.identifier, // 띠용..
                                                                    alignment: .topLeading)
   	section.boundarySupplementaryItems = [headerElement]

휴.. layout 잡아주는 부분에서 headerItem elementKind에 reuseIdentifier를 넣어주고 있... ㅠㅠ
서로 값이 다르니 당연히 오류가 날 수 밖에 ㅎㅎ

저 부분을 "UICollectionView.elementKindSectionHeader" 로 넣어주니 에러 없이 잘 나온다!

어차피 kind는 string이기 때문에 직접 string 만들어서 넣어줘도 상관은 없고 값만 서로 동일하면 에러 안남!
.
.
.

* UICollectionView.CellRegistration

이거 때문에 열심히 구글링 해보다가 cell 과 header/footer register()를 해주는 방식이 기존과는 다른게 있길래 그거도 적용해 봤는데 잘 됨! 하지만 그건 iOS 14 이상에서만 가능..ㅎ

우리 앱은 iOS 13이 최소 지원 버전이라.. 버전을 올리자 할수도 없고 ㅠㅠ
이틀 내내 고생하다 원인을 찾고나니 허무.. 앞으론 더 꼼꼼히 봐보자!

코드 ex)

	// cell registrations
    
	let topBannerCellRegistration = UICollectionView.CellRegistration<TopBannerCollectionViewCell, [Banner]> { (cell, indexPath, banner) in
		cell.setData(banners: banner)
    }

	let categoryCellRegistration = UICollectionView.CellRegistration<HomeCategoryCollectionViewCell, HomeServiceMenu> { (cell, indexPath, homeMenu) in
		// ... (생략)
	}

	let macapickCellRegistration = UICollectionView.CellRegistration<CommunityMainCollectionViewCell, Story> { (cell, indexPath, story) in
		// ... (생략)
	}

	let headerRegistration = UICollectionView.SupplementaryRegistration<HomeTitleHeaderView>(elementKind: HomeTitleHeaderView.identifier) { supplementaryView, elementKind, indexPath in
		// ... (생략)
	}
    
    // datasource에 적용
    
    viewModel.datasource = UICollectionViewDiffableDataSource<HomeSection, HomeDataItem>(collectionView: collectionView, cellProvider: { collectionView, indexPath, listItem in
		switch listItem {
		case .topBanner(let banners):
			let cell = collectionView.dequeueConfiguredReusableCell(
            	using: topBannerCellRegistration, for: indexPath, item: banners
			)
			return cell
		.
        .
        .
        (생략)
        }
	})
        

완성된 뷰 UI:

3번째 섹션에 header view 잘 적용된 거 확인~~!!

출처:
https://jamesrochabrun.medium.com/uicollectionviewdiffabledatasource-and-decodable-step-by-step-6b727dd2485
https://swiftsenpai.com/development/reload-diffable-section-header/

profile
신비로운 iOS 세계로 당신을 초대합니다.

0개의 댓글