세미나, 합동 세미나, 스터디…… 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가지 방식이 존재합니다.
UICollectionViewCompositionalLayout
NSCollectionViewCompositionalLayout
provider
클로저로 섹션마다 다양한 레이아웃을 정의할 수 있습니다. ⇒ 이제 섹션별 레이아웃이 완전히 구별될 수 있기 때문에 많은 가능성이 열립니다.예제 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
와 진짜 멋지다