마켓컬리 검색 뷰를 클론코딩하는 도중, 전체 tableView를 만들고 tableViewCell들 안에 또 collectionView를 넣는 나를 보고....
1. 이런 비효율적인 짓을 해야 하나? 라는 생각과...
2. 애플이 나보다 바보일 리가 없어......... 라는 생각이 들었고 역시나 Compositional Layout이라는 것이 있었음 🥲
Compositional Layout을 이해하기 위해, WWDC를 먼저 보기로!

복잡한 레이아웃을 쉽게 구현하고, 기존 custom layout의 단점들을 보완하여 나온 Compositional Layout

item > group > section > layout
하나의 layout에 section, section 안에 group, group 안에 item들이 있음
예시로 코드를 살짝 본다면!
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.2),
heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(0.2))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize,
subitems: [item])
let section = NSCollectionLayoutSection(group: group)
let layout = UICollectionViewCompositionalLayout(section: section)
이런 식으로 item, group, section, layout을 사용하여 compositional layout을 구현함



container의 너비의 50%만큼의 너비

container의 너비, 높이의 25%

높이를 200point로 지정


높이를 200으로 estimate(추정)한 경우





createLayout() : compositional layout 생성 함수 선언💡 흥미로운 점은 다른 레이아웃을 구성하기 위해서 이 코드를 많이 변경할 필요가 없다는 점

.fractionalWidth(0.2) 코드 사용


count parameter을 사용하여 초기화함group.interItemSpacing : group 안의 item 간격 관리 - 이 코드에서는 10포인트의 고정 간격section.interGroupSpacing : 각 group들 사이 간격 관리 - 이 코드에서는 10포인트의 고정 간격section.ContentInsets : 섹션의 inset 관리 - 이 코드에서는 section이 하나이므로 전체 레이아웃에 적용됨
// createLayout() 함수
let spacing = CGFloat(30)
group.interItemSpacing = .fixed(spacing)
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = CGFloat(10)

UICollectionViewCompositionalLayout은 provider 클로저로 섹션마다 다양한 레이아웃을 정의
라고 앞서 언급했었음

코드가 많이 달라 보이지만, 실제로는 레이아웃을 인스턴스화하는 코드일 뿐이니 겁먹지 말자!
클로저에서는,
sectionIndex : 어떤 section인지 알려주는 indexlayoutEnvironment : 다양한 유용한 속성들을 포함하는 파라미터SectionLayoutKind 관련 코드를 작성하였고, 이는 위에 enum으로 정의되어 있음
// group의 높이를 결정할 때 사용
let groupHeight = columns == 1 ?
NSCollectionLayoutDimension.absolute(44) :
NSCollectionLayoutDimension.fractionalWidth(0.2)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: groupHeight)
// horizontal group을 인스턴스화할 때 column 수를 명시적으로 전달
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: columns)

enum SectionLayoutKind: Int, CaseIterable {
case list, grid5, grid3
func columnCount(for width: CGFloat) -> Int {
let wideMode = width > 800
switch self {
case .grid3:
return wideMode ? 6 : 3
case .grid5:
return wideMode ? 10 : 5
case .list:
return wideMode ? 2 : 1
}
}
}
.
.
.
func createLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout {
(sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
guard let layoutKind = SectionLayoutKind(rawValue: sectionIndex) else { return nil }
let columns = layoutKind.columnCount(for: layoutEnvironment.container.effectiveContentSize.width)
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.2),
heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
let groupHeight = layoutKind == .list ?
NSCollectionLayoutDimension.absolute(44) : NSCollectionLayoutDimension.fractionalWidth(0.2)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: groupHeight)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: columns)
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20)
return section
}
return layout
}
let columns = layoutKind.columnCount(for: layoutEnvironment.container.effectiveContentSize.width)
columnCount 이라는 함수 선언하여, layoutEnvironment (전체 컨테이너에 대한 정보가 포함된)에서 가져오는 너비 전달enum SectionLayoutKind: Int, CaseIterable {
case list, grid5, grid3
func columnCount(for width: CGFloat) -> Int {
let wideMode = width > 800
switch self {
case .grid3:
return wideMode ? 6 : 3
case .grid5:
return wideMode ? 10 : 5
case .list:
return wideMode ? 2 : 1
}
}
}
width > 800이면, wideMode로 지정

static let badgeElementKind = "badge-element-kind"
.
.
.
// createLayout() 함수
let badgeAnchor = NSCollectionLayoutAnchor(edges: [.top, .trailing], fractionalOffset: CGPoint(x: 0.3, y: -0.3))
let badgeSize = NSCollectionLayoutSize(widthDimension: .absolute(20),
heightDimension: .absolute(20))
let badge = NSCollectionLayoutSupplementaryItem(
layoutSize: badgeSize,
elementKind: ItemBadgeSupplementaryViewController.badgeElementKind,
containerAnchor: badgeAnchor)
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.25),
heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize, supplementaryItems: [badge])
header가 고정 가능한 경우의 코드
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(44)),
elementKind: PinnedSectionHeaderFooterViewController.sectionHeaderElementKind,
alignment: .top)
let sectionFooter = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(44)),
elementKind: PinnedSectionHeaderFooterViewController.sectionFooterElementKind,
alignment: .bottom)
sectionHeader.pinToVisibleBounds = true
section.boundarySupplementaryItems = [sectionHeader, sectionFooter]
pinToVisibleBounds 를 false로




headerSize 에서 너비는 정확히 알고 있지만, 높이는 정확히 명시하지 않고 44point로 추정함
leadingItemtrailingItem 2개로 이루어져 있는 vertical trailingGroupleadingItem과 trailingGroup으로 이루어진 horizontal containterGroupAppStore 같은 거 보면,,, Nested CollectionView 그 잡채


요 코드 하나만 추가하면 됨
뭐하는 친구인지 쫌 더 보면

none: section 직각 스크롤 허용 ❌continuous: 많이 쓰는 간단하고 직관적인 스크롤continuousGroupLeadingBoundary: 직각 스크롤하여 보이는 group의 경계에서 자연스럽게 멈춤paging: 직각으로 paging 가능groupPaging: 한번에 한 group씩 직각으로 paginggroupPagingCentered: 한번에 한 group씩 직각으로 paging하며 해당 그룹을 중앙에 배치얘네에다가 물론 부수적인 코드를 추가해야 하지만 베이스 코드는 요 정도인 듯하다! dataSource까지 물론 봐야 더 이해가 잘 될 듯함
WWDC 관심 있는 주제 생기면 보겠다고 했는데 어쩌다 보니 첫 번째가 요놈이 됐다 🥲
최대한 이해해 보고 싶어서 5번 넘게 본 것 같음 ,,, 영상까지는 다 이해가 됐는데 샘플 코드를 보면 아직 많이 어려운 것 같다. ㅎㅎ,,