UICollectionViewCompositionalLayout에 관련된 내용은 아래로!
UICollectionViewCompositionalLayout
메인화면에서 작성한 컬렉션 뷰 레이아웃 관련 코드는 아래와 같습니다.
final class MainSceneViewController: UIViewController {
...
private lazy var mainCollectionView = UICollectionView(
frame: .zero,
collectionViewLayout: generateCollectionViewLayout()
)
...
}
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의 생성자 중 아래 사진의 주황색 박스 생성자를 이용하여, 섹션 별로 다른 레이아웃을 적용하는 메서드입니다.

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
}
heightDimension셀 구현 시 팝업 스토어의 제목 레이블의 numberOfLines 프로퍼티에 0을 할당하였으므로, 텍스트 길이에 따라 레이블의 줄 수가 동적으로 늘어나도록 구현하였습니다.
이를 위해 item의 heightDimension을 .estimated 로 설정하여 컨텐츠 크기(레이블의 텍스트 길이에 따른 줄 수)에 따라 높이를 동적으로 계산하도록 구현하였습니다.
<aside> 💡
구현하면서 발생한 문제
문제 상황
UICollectionViewCompositionalLayout은 섹션 → 그룹 → 아이템 순으로 크기를 계산합니다.
따라서 첫 시도에서는 group의 높이를 estimated(178)로, item의 높이를 fractionalHeight(1.0)로 설정해도 올바르게 동작할 것이라고 생각했습니다.
그러나, 레이블의 텍스트 길이가 한 줄 이상일 경우 레이블이 ...으로 잘리게 되는 문제가 발생했습니다.
원인 분석
estimated(178)은 레이블이 한 줄일 때의 높이로 설정된 값입니다.estimated는 콘텐츠 크기를 기반으로 실제 크기를 동적으로 계산합니다.
그러나, 위의 방법은 그룹의 높이를 먼저 설정한 뒤, 그룹 높이를 기준으로 아이템의 높이를 계산합니다.
이로 인해, 아이템이 동적으로 높이를 계산할 여지를 잃게 되었고, 결과적으로 레이블의 텍스트가 한 줄로 잘리는 문제가 발생했습니다.
해결책
item과 group의 높이를 모두 estimated로 설정합니다.이를 통해, 레이블의 텍스트가 여러 줄로 동적으로 표시되도록 구현할 수 있습니다.
</aside>
widthDimension그룹 사이즈를 fractionalWidth로 줄 경우 어떤 기준으로 크기가 결정될까?
그룹의 widthDimension을 fractionalWidth로 줄 경우 섹션의 크기를 기준으로 그룹의 크기가 잡히게 됩니다. generateHorizontalLayout 메서드의 경우 가로 스크롤을 설정하는 코드가 있으므로, 섹션의 가로 크기는 컨텐츠에 따라 동적으로 변하게 되어 기준이 무엇인지에 대한 궁금증이 생겼습니다.
아래와 같이 groupSize의 withDimension에 fractionalWidth(1)를 지정해보니, 아래와 같이 화면에 꽉 차는 크기였습니다.
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(178))

따라서 그룹 사이즈를 결정짓는 컨테이너의 크기는 컨텐츠 영역이 아닌 화면에 보이는 컬렉션 뷰의 영역임을 알 수 있었습니다.
가로 스크롤이 활성화 된 경우에도 그룹 사이즈를 fractional로 줄 경우, 컬렉션 뷰의 보이는 영역의 크기가 기준이 되므로, fractionalWidth로 설정하여 셀의 크기를 결정하였습니다.