
여러분, UICollectionView를 사용하면서 한 번쯤은 "이렇게 하면 더 깔끔하지 않을까?" 고민해본 적 있지 않으신가요? 특히 스크롤 뷰 안에 컬렉션 뷰를 넣었을 때 스크롤 동작이 어색하거나 오류가 발생했을 때는 문제가 더 심각하게 느껴지죠. 이번 포스트에서는 이 문제를 해결하기 위해 UICollectionView Compositional Layout으로의 전환 과정을 소개해드리고자 합니다.
UICollectionViewCompositionalLayout는 iOS 13에서 도입된 새로운 레이아웃 시스템입니다. 이 시스템은 UICollectionView의 레이아웃을 더 쉽게 구성하고, 다양한 레이아웃을 조합하여 더욱 복잡하고 유연한 사용자 인터페이스를 구현할 수 있도록 도와줍니다.
이제 실제로 어떻게 UIScrollView에서 UICollectionView로 전환했는지 코드 예제를 통해 살펴보겠습니다.
가장 먼저 해야 할 일은 UICollectionView의 레이아웃을 정의하는 것입니다. 각 섹션(Section)을 정의하고 각 섹션마다 다른 레이아웃을 적용할 수 있습니다.
private func createLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout { [weak self] sectionIndex, layoutEnvironment in
guard let self = self else { return nil }
let sectionType = self.dataSource.snapshot().sectionIdentifiers[sectionIndex]
switch sectionType {
case .navigation:
return self.createNavigationSection()
case .carousel:
return self.createCarouselSection()
case .menu:
return self.createMenuSection()
case .pharmacyMap:
return self.createPharmacyMapSection()
}
}
return layout
}
위 코드는 컴포지셔널 레이아웃을 생성하는 함수입니다. sectionType에 따라 각 섹션에 맞는 레이아웃을 반환합니다.
각 섹션의 레이아웃을 정의하여 다양한 레이아웃을 구현할 수 있습니다.
private func createNavigationSection() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(50))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(50))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: 24, leading: 24, bottom: 0, trailing: 24)
return section
}
이 코드는 네비게이션 섹션의 레이아웃을 정의합니다. 아이템과 그룹의 크기를 지정하고, 섹션의 인셋을 설정합니다.
private func createCarouselSection() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(300))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .groupPaging
section.contentInsets = NSDirectionalEdgeInsets(top: 24, leading: 0, bottom: 0, trailing: 0)
return section
}
캐러셀 섹션은 가로 스크롤이 가능한 그룹 페이징 동작을 갖도록 설정합니다. 이를 통해 캐러셀 뷰가 자연스럽게 구동됩니다.
private func createMenuSection() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .absolute(160))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(160))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item, item])
group.interItemSpacing = .fixed(16)
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: 24, leading: 24, bottom: 0, trailing: 24)
return section
}
메뉴 섹션은 두 개의 아이템이 가로로 나란히 배치되는 구조입니다.
private func createPharmacyMapSection() -> NSCollectionLayoutSection {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(160))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(160))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: 24, leading: 24, bottom: 24, trailing: 24)
return section
}
약국 지도 섹션은 단일 아이템으로 구성된 그룹입니다. 이를 통해 약국 지도를 한 화면에 집중적으로 보여줄 수 있습니다.
데이터 소스를 구성하여 각 섹션에 아이템을 추가합니다.
private func configureDataSource() {
let navigationCellRegistration = UICollectionView.CellRegistration<CustomNavigationTitleCell, String> { cell, indexPath, item in
cell.configure(with: item)
}
let carouselCellRegistration = UICollectionView.CellRegistration<CarouselCell, CarouselItem> { cell, indexPath, item in
cell.configure(with: item.content)
}.
let menuCellRegistration = UICollectionView.CellRegistration<UICollectionViewCell, String> { cell, indexPath, item in
cell.backgroundColor = .systemGray6
cell.layer.cornerRadius = 16
}
let pharmacyMapCellRegistration = UICollectionView.CellRegistration<UICollectionViewCell, String> { cell, indexPath, item in
cell.backgroundColor = .systemGray6
cell.layer.cornerRadius = 16
}
dataSource = UICollectionViewDiffableDataSource<Section, AnyHashable>(collectionView: collectionView) { collectionView, indexPath, item in
let section = self.dataSource.snapshot().sectionIdentifiers[indexPath.section]
switch section {
case .navigation:
return collectionView.dequeueConfiguredReusableCell(using: navigationCellRegistration, for: indexPath, item: item as? String)
case .carousel:
return collectionView.dequeueConfiguredReusableCell(using: carouselCellRegistration, for: indexPath, item: item as? CarouselItem)
case .menu:
return collectionView.dequeueConfiguredReusableCell(using: menuCellRegistration, for: indexPath, item: item as? String)
case .pharmacyMap:
return collectionView.dequeueConfiguredReusableCell(using: pharmacyMapCellRegistration, for: indexPath, item: item as? String)
}
}
var snapshot = NSDiffableDataSourceSnapshot<Section, AnyHashable>()
snapshot.appendSections([.navigation, .carousel, .menu, .pharmacyMap])
snapshot.appendItems(["Navigation"], toSection: .navigation)
snapshot.appendItems(["Search", "Notification"], toSection: .menu)
snapshot.appendItems(["Pharmacy Map"], toSection: .pharmacyMap)
dataSource.apply(snapshot, animatingDifferences: false)
setupInfiniteScroll()
}
이 코드에서는 네 가지 섹션에 대해 셀을 등록하고, 스냅샷을 통해 섹션과 아이템을 추가합니다.
| UIScrollView | Compositional Layout |
|---|---|
![]() | ![]() |
UICollectionView Compositional Layout을 이용하면 복잡한 레이아웃도 쉽게 구성할 수 있으며, 다양한 섹션을 한 화면에 배치해도 자연스럽게 동작합니다. 이번 포스트에서 소개한 예제 코드를 통해 여러분도 프로젝트에 유연한 레이아웃을 구현해보세요! 새로운 레이아웃 시스템을 통한 더 나은 사용자 경험을 기대해봅시다. 🚀