난 Compositional Layout을 다룰줄 몰랐다
정확히는 Compositional Layout으로 collectionView를 만들었지만 한 collection view 안에 여러 섹션으로 모양을 다양하게 다룰줄 몰랐다.
그런데 이번에 디자인 기획안이 너무나도 Compositional Layout으로 구현하기 좋게 나왔고 안드로이드 개발자분한테 swift에는 이런 방식이 있다 말씀드리니 자기도 그런 비슷한 걸 시도해볼까 한다고 하셔서 같이 도전하기로 했다(물론 각자지만 마음만은 ㅋ)
맨 하단에 전체 코드를 첨부할거지만 사실 뭘... 딱히 이해하기에 크게 어렵진 않았던 것 같다
왜냐면 단일 collectionView 선언에서 section만 여러개 만드는 느낌? 인 것 같아서 그럴수도..
(그냥 평소에 겁먹고 공부하기를 미뤘는데 반성한다...!)
각설하고 가장 중요한 부분은 CompositionalLayout을 반환하는 부분인 것 같은데
private func createLayout() -> UICollectionViewCompositionalLayout {
return UICollectionViewCompositionalLayout { [weak self] (sectionIndex, _) -> NSCollectionLayoutSection? in
switch sectionIndex {
case 0:
return self?.firstPurchaseSectionLayout()
case 1:
return self?.sixItemsSectionLayout()
default:
return self?.threeItemsSectionLayout()
}
}
}
이렇게 section Index에 따라 각각의 sectionLayout을 반환해 그에 맞는 (내가 원하는?) 디자인으로 collection view를 만들 수 있다
결과 화면
전체 코드
- CollectionView 선언 부
import UIKit
import SnapKit
import SwiftUI
final class HomeSectionView: UIView {
init() {
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private lazy var sectionCollectionView = UICollectionView()
func setViewContens() {
self.sectionCollectionView = createSectionCollectionView()
setLayout()
}
private func setLayout() {
self.addSubview(sectionCollectionView)
sectionCollectionView.snp.makeConstraints {
$0.top.leading.trailing.bottom.equalToSuperview()
$0.height.equalTo(calculateCVHeigt())
}
}
let sections: [SectionData] = HomeSectionDummyData.sections
}
extension HomeSectionView: UICollectionViewDataSource, UICollectionViewDelegate {
func calculateCVHeigt() -> Double {
var height = 0.0
for section in self.sections {
height += section.sectionCase.height
}
return height
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return sections.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return sections[safe: section]?.cellInfo.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "\(HomeFirstPurchaseCVCell.self)", for: indexPath) as? HomeFirstPurchaseCVCell,
let cellInfo = sections[safe:indexPath.section]?.cellInfo[safe: indexPath.row] else {
return UICollectionViewCell()
}
cell.setCellContents(color: cellInfo)
return cell
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if kind == UICollectionView.elementKindSectionHeader {
if indexPath.section == 0 {
guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "\(secondHeaderView.self)", for: indexPath) as? secondHeaderView else {
return secondHeaderView()
}
return header
} else {
guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "\(firstHeaderView.self)", for: indexPath) as? firstHeaderView else {
return firstHeaderView()
}
return header
}
} else {
return firstHeaderView()
}
}
}
//MARK: - make collectionView layout
extension HomeSectionView {
private func createSectionCollectionView() -> UICollectionView {
let layout = createLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(HomeFirstPurchaseCVCell.self, forCellWithReuseIdentifier: "\(HomeFirstPurchaseCVCell.self)")
collectionView.register(firstHeaderView.self,
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: "\(firstHeaderView.self)")
collectionView.register(secondHeaderView.self,
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: "\(secondHeaderView.self)")
collectionView.isScrollEnabled = false
return collectionView
}
private func createLayout() -> UICollectionViewCompositionalLayout {
return UICollectionViewCompositionalLayout { [weak self] (sectionIndex, _) -> NSCollectionLayoutSection? in
switch sectionIndex {
case 0:
return self?.firstPurchaseSectionLayout()
case 1:
return self?.sixItemsSectionLayout()
default:
return self?.threeItemsSectionLayout()
}
}
}
private func firstPurchaseSectionLayout() -> NSCollectionLayoutSection {
let size = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(0.8))
let item = NSCollectionLayoutItem(layoutSize: size)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(0.8))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .groupPaging
return section
}
private func sixItemsSectionLayout() -> NSCollectionLayoutSection {
let size = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1 / 2), heightDimension: .fractionalWidth(1 / 2))
let item = NSCollectionLayoutItem(layoutSize: size)
item.contentInsets = .init(top: 0, leading: 5, bottom: 16, trailing: 5)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
heightDimension: .fractionalWidth(1 / 2))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
let sectionHeaderSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(60))
section.boundarySupplementaryItems = [.init(layoutSize: sectionHeaderSize,
elementKind: UICollectionView.elementKindSectionHeader,
alignment: .topLeading)]
section.contentInsets = .init(top: 0, leading: 15, bottom: 0, trailing: 15)
return section
}
private func threeItemsSectionLayout() -> NSCollectionLayoutSection {
let size = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(0.3))
let item = NSCollectionLayoutItem(layoutSize: size)
item.contentInsets = .init(top: 5, leading: 20, bottom: 5, trailing: 20)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
heightDimension: .fractionalWidth(0.3))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
let sectionHeaderSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(50))
section.boundarySupplementaryItems = [.init(layoutSize: sectionHeaderSize,
elementKind: UICollectionView.elementKindSectionHeader,
alignment: .topLeading)]
return section
}
}
final class firstHeaderView: UICollectionReusableView {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .red
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
final class secondHeaderView: UICollectionReusableView {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .blue
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
- cell 선언부
import UIKit
import SnapKit
final class HomeFirstPurchaseCVCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setCellContents(color: UIColor) {
self.backgroundColor = color
}
}
- Section Data 선언부(현재는 더미 데이터지만 추후 request 구현 후 대체 예정)
import UIKit
struct HomeSectionDummyData {
static let sections = [
SectionData(sectionCase: .firstPurchase, title: "첫 구매 ONLY 990원 특가!", cellInfo: [.systemBlue ]), //firstSection
SectionData(sectionCase: .sixItemSection, title: "0원 샘플로 제품을 체험해 보세요!", cellInfo: [.red, .orange, .yellow, .green, .blue, .purple]),
SectionData(sectionCase: .threeItemSection, title: "다른 사람들은 이런 화장품을 보고 있어요", cellInfo: [.systemGreen, .mainBlue, .warning])
]
}
struct SectionData {
let sectionCase: SectionCase
let title: String?
let cellInfo: [UIColor]
}
enum SectionCase {
case firstPurchase
case sixItemSection
case threeItemSection
//heigt의 경우 figma디자인에 따라 높이 그대로 가도 되면 figma의 높이로
//높이를 그대로 가져가기 애매할 경우 계상된 높이로
var height: Double {
switch self {
case .firstPurchase:
return (UIScreen.main.bounds.width * 0.8)
case .sixItemSection:
return (UIScreen.main.bounds.width * 1.55)
case .threeItemSection:
return (UIScreen.main.bounds.width * 1.02)
}
}
}
추가로 group을 horizontal이 아닌 vertical로 구현하면
이렇게도 가넝
참고
'Swift > UIKit' 카테고리의 다른 글
높이 조절 가능한 CustomModal(Half Modal) (1) | 2024.02.13 |
---|---|
Tableview pagination (0) | 2024.01.24 |
높이가 다른 이미지 다운로드 후 Cell에 할당 (0) | 2024.01.09 |
스크롤 중 타이머로 인한 뷰 업데이트가 멈추는 현상 (1) | 2024.01.03 |