세미나, 합동 세미나, 스터디…… SOPT에서 많은 활동을 하면서 이전과는 비교도 안 되는 복잡한 뷰들을 많이 만들어 봤던 것 같아요.
그 중 하나가 이 뷰였는데요. 여러분들은 이 뷰를 어떻게 구현하시나요? 저는 전체 tableView를 만들고, 각 tableViewCell들에 collectionView를 넣는 방법을 사용했어요. 혹은 첫 번째와 세 번째는 header, footer로 구현하면 그래도 고생은 덜하지 않을까? 이런 고민을 했었던 기억이 나요.
열심히 tableViewCell을 만들고, 그 안에 다시 collectionView를 넣던 와중에 문득 이런 의문이 들었어요. 아니 왜 이렇게 비효율적인 짓을 하지… 요즘 시대에 뭐라도 만들어 놨어야 하는 거 아닌가… 라는 생각에 검색을 해 보니 아니나 다를까 이미 Compositional Layout 이라는 게 나와 있더라구요?
(단, iOS 13 이상만 지원 💦)
어쨌든 제가 당장 공부해 봤습니다 ~ 😎
당장 앱스토어만 봐도 요즘 앱의 디자인들이 얼마나 복잡한지 알 수 있어요. 앱스토어를 tableView와 collectionView가 중첩되는 뷰로 구현해야 한다고 생각하면 정말 머리가 아픕니다. 이를 보완하기 위해 flexible하고 빠르게 어떤 레이아웃이든 만들 수 있는 compositional layout이 탄생하게 됩니다.
Compositional Layout은 item, group, section, layout으로 구성됩니다. 하나의 layout에 section, section 안에 group, group 안에 item들이 있어요. 각 요소들의 size를 정하고 지정해 주기만 하면 레이아웃이 완성돼요.
각 요소들의 size를 정해 주는 방법은 3가지가 존재합니다.
fractionalWidth & fractionalHeight - 컨테이너와의 너비&높이 비율absolute - 포인트값으로 지정estimated - 후에 content의 크기가 바뀌어 크기가 정확하지 않을 때는 estimate 값으로 let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.2),
heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item은 group 안에 존재해요. 따라서 item의 size는 group과의 비율로 나타내게 됩니다. 그렇다면 이 코드에서 item의 너비는 group 너비의 20%, 높이는 group 높이의 100%가 되겠죠. 그리고 이러한 size로 item을 구성하겠다고 지정해 주기만 하면 됩니다. group과 section 모두 이런 식으로 구성하면 됩니다.
지정된 방식에 따라 item들을 배치합니다. 3가지 방식이 존재합니다.
UICollectionViewCompositionalLayoutNSCollectionViewCompositionalLayoutprovider 클로저로 섹션마다 다양한 레이아웃을 정의할 수 있습니다. ⇒ 이제 섹션별 레이아웃이 완전히 구별될 수 있기 때문에 많은 가능성이 열립니다.예제 2개를 살펴보고 마무리 하겠습니다.
Compositional Layout을 사용하여 Grid 형태를 구현해 볼게요.
private func createLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.2),
heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
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)
return layout
}
여러 개의 section을 가진 뷰를 만들어 볼게요.
enum SectionLayoutKind: Int, CaseIterable {
case list, grid1, grid2
var columnInt: Int {
switch self {
case .list:
return 1
case .grid1:
return 5
case .grid2:
return 2
}
}
}
섹션이 여러 개이므로, enum으로 만들어 관리해 줍니다. columnInt는 각 section에 나타낼 열의 개수를 표시합니다. list 섹션의 column은 1이 되고, grid1의 열은 5개, grid5의 열은 2개가 되도록 했습니다.
private func createLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
guard let sectionLayoutKind = SectionLayoutKind(rawValue: sectionIndex) else {return nil}
// print(sectionLayoutKind)
let columns = sectionLayoutKind.columnInt
// print(columns)
var itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0))
if columns == 5 {
itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.2),
heightDimension: .fractionalHeight(1.0))
} else if columns == 2 {
itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5),
heightDimension: .fractionalHeight(1.0))
}
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
let groupHeight = columns == 1 ?
NSCollectionLayoutDimension.absolute(44) :
NSCollectionLayoutDimension.fractionalWidth(0.2)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: groupHeight)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, repeatingSubitem: item, count: columns)
let section = NSCollectionLayoutSection(group: group)
return section
}
return layout
}
provider 클로저로 섹션마다 다양한 레이아웃을 정의할 수 있다고 앞서 언급했었는데요. 이번 예제 같은 여러 개의 섹션을 구현할 때 사용됩니다.init(sectionProvider: UICollectionViewCompositionalLayoutSectionProvider)
groupHeight 상수를 만들어 열이 1개일 시와 아닐 때로 나누어 size를 지정해 주었습니다. 이런 식으로 레이아웃이 완전히 바뀌어도 레이아웃을 정의하는 코드 자체는 크게 변화하지 않는다는 게 정말 흥미로운 점 같아요.
WWDC를 보면 더 많은 예제들과 DataSource를 사용하는 새로운 방법에 대한 설명도 있어요.
관련 링크를 두고 이만 글을 마치겠습니다. 👋🏻
💻 WWDC 19 - Advances in Collection View Layout
💻 WWDC 19 - Advances in UI Data Sources
💻 WWDC 20 - Advances in UICollectionView
와 진짜 멋지다