본문 바로가기
Swift/UIKit

[UIKit] UICollectionView Compostional Layout 으로 TableView만들기 (WWDC20 List in UIColelctionView )

by 마라민초닭발로제 2024. 1. 24.

UICollectionView Compostional Layout 으로 TableView만들기 (WWDC20 List in UIColelctionView )

글 작성 개요

CompositionalLyaout이 UITableView를 완전하게 대체할 수 있지 않을까라는 조그마한 의문에서 시작하게 되었음. 그래서 관련 세션이 있나 찾아 보았는데 WWDC20 List in UICollectionView를 보게 되었고 이에 대한 내용을 정리하기로 했음

Modern Collection Views

UICollectionView를 구성하는 API는 데이터, 레이아웃, 프레젠테이션의 세 가지 카테고리로 구분할 수 있습니다. UICollectionView의 새로운 개념 중 하나는 콘텐츠가 렌더링되는 '위치'인 레이아웃과 '무엇'인 데이터를 분리하는 것이었습니다. 이러한 구분은 UICollectionView를 유연하게 만드는 핵심 요소입니다.

FLowLayout은 CollectionViewDataSource를 제공하는 Delegate를 통해서 관리를 할 수 있습니다. CompostionalLyaout은 DiffableDataSource를 통해서 Delegate보다 쉽게 CollectionView의 DataSource를 건들 수 있습니다.

basic iOS13 이전 FlowLayout iOS13 이후 CompositionalLyaout



CellRegistration

보통 CollectionView를 사용하기 위해 Cell를 Registration하고는 합니다. 이 Cell Registration에서는 몇가지 불편한 점이 있습니다.

  • unique Identifer를 필수적으로 만들어야 합니다.
  • 또한 Cell을 dequeue할 떄 타입 캐스팅을 해야 합니다. ex) guard let ... = ... as? CustomCell else { }

 

이를 간단하게 하는 방법으로 UICollectionView.CellRegistration을 활용합니다. 이를 코드를 통해서 설명하겠습니다.

Register 방식

아래는 identifer를 통해서 Cell을 등록하는 과정입니다.

var dataSrouce: UICollectionViewDiffableDataSource?

func setupDataSource() {
    collectionView.register(anyClass, identifier: String) 
    dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in
    guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CustomCell.identifier, for: indexPath) as? CustomCell else {
        return ...
    }
    cell.configure(...)
    return cell
    }
}

UICollectionView.CellRegistration 소개

다음은 CellRegisteration 방식입니다.

let cellRegistration = UICollectionView.CellRegistration<CustomCell, Int> { cell, indexPath, itemIdentifier in
    //TODO: Some Configureation must be write for CollectionViewCell
  }
func setupDataSource() {
    collectionView.register(anyClass, identifier: String) 
    dataSource = .init(collectionView: collectionView) { [weak self] collectionView, indexPath, itemIdentifier in
        guard let self else { return UICollectionViewCell() }
        let cell = collectionView.dequeueConfiguredReusableCell(using: self.cellRegistration, for: indexPath, item: itemIdentifier)
        return cell
    }
}

 

UICollectionView.CellRegistration 이점

이러한 방식은 CompostionalLayout에 Multiple Section에서 매우 빛을 바랍니다. 다음 화면을 통해서 설명드리겠습니다.

   

위와 같이 Multiple Section일 경우에는 각기 다른 Section에 View를 Configure하는 다른 방법을 해야 합니다. 물론 이전에 등록했던 것 처럼 할 수 있지만, 애플에서 소개한 CollectionView.registration을 통해서 하면 간단하게 구현 할 수 있습니다.

TableView Section 만들기

UICollectionViewCompositionalLayoutSectionProvider을 활용하여 CompostionalLayout을 만들면 됩니다.

let layout = UICollectionViewCompositionalLayout() {
    [weak self] sectionIndex, layoutEnvironment in
    guard let self = self else { return nil }

    // @todo: add custom layout sections for various sections

    let configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
    let section = NSCollectionLayoutSection.list(using: configuration, layoutEnvironment: layoutEnvironment)
    return section
}

UICollectionLayoutListConfiguration(appearance:)의 appearance의 타입은 자주 사용하는 UITableView와 매우 유사합니다.

공식 문서에는 다음과 같이 기재되어 있습니다.

  • case plain
    • The plain list appearance.
  • case grouped
    • The grouped list appearance.
  • case insetGrouped
    • The inset grouped list appearance.
  • case sidebar
    • The sidebar list appearance.
  • case sidebarPlain
    • The plain sidebar list appearance.

실제 코드로 보면 다음과 같습니다.

palin grouped insetGrouped sidebar sidebarPlain
plain group insetGrouped sidebar sidebarPlain

DiffableDataSource에 Header와 Footer 추가하기

정통 방식

WWDC에서 diffableDataSource를 통해서 Header와 Footer를 추가하는 방법을 다음과 같이 설명했습니다. layout을 통해서 생성엉된 supplementaryView를 다음과 같이 dequeue해서 보여줍니다.

dataSource.supplementaryViewProvider = { (collectionView, elementKind, indexPath) in
    if elementKind == UICollectionView.elementKindSectionHeader {
        return collectionView.dequeueConfiguredReusableSupplementary(using: header, for: indexPath)
    }
    else {
        return nil
    }
}

 

헤더 추가하기

Supplementary

UICollectionLayoutListConfiguration의 HeaderMode를 적절하게 조정하여 헤더를추가할 수 있습니다.

var configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
configuration.headerMode = .supplementary
let layout = UICollectionViewCompositionalLayout.list(using: configuration)

dataSource.supplementaryViewProvider = { (collectionView, elementKind, indexPath) in
    if elementKind == UICollectionView.elementKindSectionHeader {
        return collectionView.dequeueConfiguredReusableSupplementary(using: header, for: indexPath)
    }
    else {
        return nil
    }
}

 

UICollectionLayoutListConfiguration.HeaderMode.firstItemInSection

HeaderMode.firstItemInSection을 통해서 첫 번째 아이템을 Header로 만들 수 있습니다.

var configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
configuration.headerMode = .firstItemInSection
let layout = UICollectionViewCompositionalLayout.list(using: configuration)

레퍼런스

cellregistration

UICollectionLayoutSectionOrthogonalScrollingBehavior

UicolleCtionViewCompositionalLayout

wwdc 20 List in UICollectionView