Compositional Layout을 사용해보신 분들은 아시겠지만 주로 쓰이는 코드의 형태가 비슷합니다.
저는 이 부분을 함수화 해서 코드의 중복과 가독성을 높이고자 했습니다.
// CompositionalLayoutFactory.swift
enum SupplementaryItemType {
case header
case footer
}
// MARK: - Compositional Layout
class CompositionalLayoutFactory {
/// Item 생성 시 사용합니다.
func createItem(
widthDimension: NSCollectionLayoutDimension,
heightDimension: NSCollectionLayoutDimension,
contentInsets: NSDirectionalEdgeInsets = .zero
) -> NSCollectionLayoutItem {
let itemSize = NSCollectionLayoutSize(widthDimension: widthDimension, heightDimension: heightDimension)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = contentInsets
return item
}
/// Group 생성 시 사용합니다.
func createGroup(
item: [NSCollectionLayoutItem],
widthDimension: NSCollectionLayoutDimension,
heightDimension: NSCollectionLayoutDimension,
contentInsets: NSDirectionalEdgeInsets = .zero
) -> NSCollectionLayoutGroup {
let groupSize = NSCollectionLayoutSize(widthDimension: widthDimension, heightDimension: heightDimension)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: item)
group.contentInsets = contentInsets
return group
}
/// Header 또는 Footer 생성 시 사용합니다.
/// - type에 .header 또는 .footer를 작성
func createBoundarySupplementaryItem(
type: SupplementaryItemType,
widthDimension: NSCollectionLayoutDimension,
heightDimension: NSCollectionLayoutDimension,
alignment: NSRectAlignment = .top
) -> NSCollectionLayoutBoundarySupplementaryItem {
return NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: .init(
widthDimension: widthDimension,
heightDimension: heightDimension
),
elementKind: type == .header ? UICollectionView.elementKindSectionHeader : UICollectionView.elementKindSectionFooter,
alignment: alignment
)
}
/// Section 생성 시 사용합니다.
func createLayoutSection(
group: NSCollectionLayoutGroup,
orthogonalScrollingBehavior: UICollectionLayoutSectionOrthogonalScrollingBehavior = .none,
sectionContentInsets: NSDirectionalEdgeInsets = .zero,
boundarySupplementaryItems: [NSCollectionLayoutBoundarySupplementaryItem]? = nil
) -> NSCollectionLayoutSection {
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = orthogonalScrollingBehavior
section.contentInsets = sectionContentInsets
if let supplementaryItems = boundarySupplementaryItems {
section.boundarySupplementaryItems = supplementaryItems
}
return section
}
}
CompositionalLayoutFactory라는 파일을 따로 만들어서 클래스 안에 item, group, section, header 및 footer를 만드는 함수를 작성했습니다.
그리고 제보하기 화면(ReportViewController)에서 collectionView를 아래와 같이 생성하는데요!
// ReportViewController.swift
private let compositionalfactory: ReportCompositionalLayoutFactory = ReportCompositionalLayoutFactory()
private lazy var compositionalLayout: UICollectionViewCompositionalLayout = compositionalfactory.create()
private lazy var collectionView: UICollectionView = UICollectionView(frame: .zero, collectionViewLayout: compositionalLayout)
UICollectionView의 collectionViewLayout 인자로 들어가는 UICollectionViewCompositionalLayout 타입을
ReportCompositionalLayoutFactory라는 인스턴스의 create 함수로부터 얻습니다.
이제부터 이 ReportCompositionalLayoutFactory 클래스를 설명드릴게요!
코드는 아래와 같습니다.
// ReportCompositionalLayoutFactory.swift
enum ReportSectionType: Int {
case search
case category
case image
case menu
case addMenu
}
final class ReportCompositionalLayoutFactory: CompositionalLayoutFactory {
var isImageSelected: Bool = false
func create() -> UICollectionViewCompositionalLayout {
return UICollectionViewCompositionalLayout { [self] (sectionIndex, _) -> NSCollectionLayoutSection? in
guard let sectionType: ReportSectionType = ReportSectionType(rawValue: sectionIndex) else {
return nil
}
let section: NSCollectionLayoutSection
switch sectionType {
case .search:
section = getSearchLayoutSection()
case .category:
section = getCategoryLayoutSection()
case .image:
section = getImageLayoutSection()
case .menu:
section = getMenuLayoutSection()
case .addMenu:
section = getAddMenuLayoutSection()
}
return section
}
}
}
extension ReportCompositionalLayoutFactory {
// MARK: - Search Section
func getSearchLayoutSection() -> NSCollectionLayoutSection {
let item = createItem(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(112))
let group = createGroup(item: [item], widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(112))
let section = createLayoutSection(group: group, orthogonalScrollingBehavior: .groupPaging)
return section
}
// MARK: - Category Section
func getCategoryLayoutSection() -> NSCollectionLayoutSection {
let header = createBoundarySupplementaryItem(type: .header, widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(50), alignment: .topLeading)
let item = createItem(widthDimension: .estimated(50), heightDimension: .fractionalHeight(1))
let group = createGroup(item: [item], widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(36))
group.interItemSpacing = .fixed(6)
let section = createLayoutSection(group: group, sectionContentInsets: .init(top: 16, leading: 22, bottom: 24, trailing: 10), boundarySupplementaryItems: [header])
section.interGroupSpacing = 8
return section
}
// MARK: - Image Section
func getImageLayoutSection() -> NSCollectionLayoutSection {
let header = createBoundarySupplementaryItem(type: .header, widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(53), alignment: .topLeading)
let item = createItem(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(21 + 24 + (isImageSelected ? 84 : 58) + 24))
let group = createGroup(item: [item], widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(21 + 24 + (isImageSelected ? 84 : 58) + 24))
let section = createLayoutSection(group: group, sectionContentInsets: .init(top: 0, leading: 22, bottom: 0, trailing: 22), boundarySupplementaryItems: [header])
return section
}
// MARK: - Menu Section
func getMenuLayoutSection() -> NSCollectionLayoutSection {
let item = createItem(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(95))
let group = createGroup(item: [item], widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(95))
let section = createLayoutSection(group: group, sectionContentInsets: .init(top: 0, leading: 22, bottom: 0, trailing: 14))
section.interGroupSpacing = 4
return section
}
// MARK: - Add Menu Section
func getAddMenuLayoutSection() -> NSCollectionLayoutSection {
let footer = createBoundarySupplementaryItem(type: .footer, widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(80), alignment: .bottom)
let item = createItem(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(32))
let group = createGroup(item: [item], widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(32))
let section = createLayoutSection(group: group, sectionContentInsets: .init(top: 8, leading: 14, bottom: 52, trailing: 22), boundarySupplementaryItems: [footer])
return section
}
}
이 ReportCompositionalLayoutFactory는 위에서 만들었던 CompositionalLayoutFactory 클래스를 상속받습니다.
이를 통해 CompositionalLayoutFactory 클래스 안에 있는 함수를 사용할 수 있어요.
우선 제보하기 섹션은 총 5개이고 ReportSectionType라는 enum으로 타입을 설정합니다.
create 함수에서는 UICollectionViewCompositionalLayout 타입을 반환합니다. 섹션마다 다른 함수를 호출해서 section 값을 할당하고 반환합니다.
섹션이 총 5개로 각각 다른 함수를 통해 section 값을 받아옵니다.
예를 들어 getCategoryLayoutSection 함수를 설명하자면 이 함수는 세번째 섹션을 만드는 함수입니다.
세번째 섹션의 구조는 이와 같아요. header와 item이 필요합니다!
// MARK: - Category Section
func getCategoryLayoutSection() -> NSCollectionLayoutSection {
let header = createBoundarySupplementaryItem(type: .header, widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(50), alignment: .topLeading)
let item = createItem(widthDimension: .estimated(50), heightDimension: .fractionalHeight(1))
let group = createGroup(item: [item], widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(36))
group.interItemSpacing = .fixed(6)
let section = createLayoutSection(group: group, sectionContentInsets: .init(top: 16, leading: 22, bottom: 24, trailing: 10), boundarySupplementaryItems: [header])
section.interGroupSpacing = 8
return section
}
그래서 함수 안에서 CompositionalLayoutFactory에 있는 함수를 통해 header를 만들고, item과 group, section을 순서대로 레이아웃을 맞춰 만들 수 있습니다.
이렇게 섹션에 따라 다른 함수를 호출함으로써 create 함수의 가독성을 높일 수 있습니다.
이러한 방식으로 한끼족보 iOS는 Compositional Layout 작성 코드를 분산시켜 파일 하나가 너무 길어지는 것을 방지하고, 불필요한 로직을 숨겼습니다.
아직 많이 부족하지만 앞으로 더 좋은 코드를 만들어나가는 개발자가 되도록 노력하겠습니다!
한끼족보 iOS의 깃허브 레포지토리를 첨부해둘 테니 많이 봐주시고 피드백 주시면 감사하겠습니다 🧡
'iOS' 카테고리의 다른 글
한끼족보 iOS의 프로젝트 초기세팅 (3) | 2024.08.08 |
---|