UICollectionViewCompositionalLayout에 관련된 내용은 아래로!

UICollectionViewCompositionalLayout

메인화면에서의 Layout

메인화면에서 작성한 컬렉션 뷰 레이아웃 관련 코드는 아래와 같습니다.

1. 컬렉션 뷰 정의 부분

final class MainSceneViewController: UIViewController {
		...
    private lazy var mainCollectionView = UICollectionView(
        frame: .zero,
        collectionViewLayout: generateCollectionViewLayout()
    )
    ... 
}

generateCollectionViewLayout() 이라는 메서드를 호출하여 컬렉션 뷰의 레이아웃을 지정합니다.

2. generateCollectionViewLayout()

private func generateCollectionViewLayout() -> UICollectionViewCompositionalLayout {
    return UICollectionViewCompositionalLayout { [weak self] sectionIndex, _ in
        guard let self else { return nil }

        switch sectionIndex {
        case 0:
            return generateHorizontalLayout(isPickSection: true)
        case 1..<(1 + self.mainViewModel.getDataSource().numbersOfInterest()):
            return generateHorizontalLayout()
        case self.mainViewModel.getDataSource().numbersOfInterest() + 1:
            return generateVerticalGridLayout()
        default:
            return generateHorizontalLayout()
        }
    }
}

UICollectionViewCompositionalLayout의 생성자 중 아래 사진의 주황색 박스 생성자를 이용하여, 섹션 별로 다른 레이아웃을 적용하는 메서드입니다.

스크린샷 2024-12-12 오후 6.12.29.png

3. generateHorizontalLayout()

private func generateHorizontalLayout(isPickSection: Bool = false) -> NSCollectionLayoutSection {
    let itemSize = NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(1.0),
        heightDimension: isPickSection ? .estimated(160) : .estimated(180)
    )
    let item = NSCollectionLayoutItem(layoutSize: itemSize)

    let groupSize = NSCollectionLayoutSize(
        widthDimension: isPickSection ? .fractionalWidth(140/393) : .fractionalWidth(160/393),
        heightDimension: isPickSection ? .estimated(160) : .estimated(180)
    )
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])

    let section = NSCollectionLayoutSection(group: group)
    section.contentInsets = NSDirectionalEdgeInsets(top: 35, leading: 26, bottom: 25, trailing: 0)
    section.interGroupSpacing = 10

    section.orthogonalScrollingBehavior = .continuous

    let headerSize: NSCollectionLayoutSize

    if isPickSection {
        headerSize = NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(1.0),
            heightDimension: .fractionalWidth(446/393))
    } else {
        headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(44))
    }

    let headerSupplementary = NSCollectionLayoutBoundarySupplementaryItem(
        layoutSize: headerSize,
        elementKind: UICollectionView.elementKindSectionHeader,
        alignment: .top
    )
    section.boundarySupplementaryItems = [headerSupplementary]
    section.supplementaryContentInsetsReference = .none

    return section
}

1) 아이템과 그룹의 heightDimension

셀 구현 시 팝업 스토어의 제목 레이블의 numberOfLines 프로퍼티에 0을 할당하였으므로, 텍스트 길이에 따라 레이블의 줄 수가 동적으로 늘어나도록 구현하였습니다.

이를 위해 itemheightDimension.estimated 로 설정하여 컨텐츠 크기(레이블의 텍스트 길이에 따른 줄 수)에 따라 높이를 동적으로 계산하도록 구현하였습니다.

<aside> 💡

구현하면서 발생한 문제

문제 상황

UICollectionViewCompositionalLayout은 섹션 → 그룹 → 아이템 순으로 크기를 계산합니다.

따라서 첫 시도에서는 group의 높이를 estimated(178)로, item의 높이를 fractionalHeight(1.0)로 설정해도 올바르게 동작할 것이라고 생각했습니다.

그러나, 레이블의 텍스트 길이가 한 줄 이상일 경우 레이블이 ...으로 잘리게 되는 문제가 발생했습니다.

원인 분석

estimated는 콘텐츠 크기를 기반으로 실제 크기를 동적으로 계산합니다. 그러나, 위의 방법은 그룹의 높이를 먼저 설정한 뒤, 그룹 높이를 기준으로 아이템의 높이를 계산합니다. 이로 인해, 아이템이 동적으로 높이를 계산할 여지를 잃게 되었고, 결과적으로 레이블의 텍스트가 한 줄로 잘리는 문제가 발생했습니다.

해결책

이를 통해, 레이블의 텍스트가 여러 줄로 동적으로 표시되도록 구현할 수 있습니다.

</aside>

2) 아이템과 그룹의 widthDimension