swift

RxDataSources로 multiple section collection view 구현하기

kimyounggyun 2022. 8. 20. 18:33

모델 정의

collectionview cell에 들어갈 데이터의 모델을 정의한다.

struct Model {
    var color: Int
}

SectionModel 정의

section을 구분할 열거형을 만들고 SectionModelType 프로토콜을 채택한다. Item의 타입과 items를 구현해야 한다.
Item typealias를 정의한다. Item typealias는 section안에 들어갈 item의 타입과 동일하다.

public protocol SectionModelType {
    associatedtype Item
    var items: [Item] { get }
    init(original: Self, items: [Item])
}

다중 섹션을 구현할 것이기 때문에 열거형으로 각 section에 들어갈 item과 SectionModel를 정의한다.

import RxDataSources

enum SectionItem {
    case firstItem(model: Model)
    case secondItem(model: Model)
}

enum CollectionViewSectionModel {
    case firstSection(items: [SectionItem])
    case secondSection(items: [SectionItem])
}

extension CollectionViewSectionModel: SectionModelType {
    typealias Item = SectionItem

    var items: [SectionItem] {
        switch self {
        case .firstSection(let items):
            return items
        case .secondSection(let items):
            return items
        }
    }

    init(original: Self, items: [Self.Item]) {
        self = original
    }
}

datasource 만들기

위에서 만든 sectionModel 타입의 dataSource 객체를 만든다.

typealias DataSource = RxCollectionViewSectionedReloadDataSource
let dataSource = DataSource<CollectionViewSectionModel> { dataSource, collectionView, indexPath, item in
    switch dataSource[indexPath] {
    case .firstItem(let model):
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: OneCollectionViewCell.identifier,
                                                            for: indexPath) as? OneCollectionViewCell else {
            return UICollectionViewCell()
        }
        cell.backgroundColor = UIColor(hex: model.color)
        return cell
    case .secondItem(let model):
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TwoCollectionViewCell.identifier,
                                                            for: indexPath) as? TwoCollectionViewCell else {
            return UICollectionViewCell()
        }
        cell.backgroundColor = UIColor(hex: model.color)
        return cell
    }
}

collection view에 전달할 데이터 만들기

let hex = [0x779c74, 0xcaa94d, 0x32c96e, 0xd499d4, 0xe2cce7,
           0xa8daf9, 0xe85255, 0xe3272b, 0x7f0909, 0xdedede]

var firstItems = [SectionItem]()
var secondItems = [SectionItem]()

hex.forEach{ firstItems.append(.firstItem(model: Model(color: $0))) }
hex.forEach{ secondItems.append(.secondItem(model: Model(color: $0))) }

let colorObservable = BehaviorSubject<[CollectionViewSectionModel]>(value: [
    .firstSection(items: firstItems),
    .secondSection(items: secondItems)
])

var items: Driver<[CollectionViewSectionModel]>
var dataSource: DataSource<CollectionViewSectionModel>

self.items = colorObservable.asDriver(onErrorJustReturn: [])
self.dataSource = dataSource

binding

 viewModel.outputs.items
    .drive(collectionView.rx.items(dataSource: viewModel.outputs.dataSource))
    .disposed(by: rx.disposeBag)

compositional layout code

func layout() -> UICollectionViewCompositionalLayout {
    let layout = UICollectionViewCompositionalLayout { section, _ in
        switch section {
        case 0:
            let item = NSCollectionLayoutItem(
                layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                   heightDimension: .fractionalHeight(1.0)))
            item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)

            let verticalGroup = NSCollectionLayoutGroup.vertical(
                layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                   heightDimension: .absolute(360)),
                subitem: item,
                count: 3)

            let horizontalGroup = NSCollectionLayoutGroup.horizontal(
                layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.95),
                                                   heightDimension: .absolute(360)),
                subitem: verticalGroup,
                count: 1)

            let section = NSCollectionLayoutSection(group: horizontalGroup)
            section.orthogonalScrollingBehavior = .groupPaging
            return section
        case 1:
            let item = NSCollectionLayoutItem(
                layoutSize: NSCollectionLayoutSize(widthDimension: .absolute(200),
                                                   heightDimension: .absolute(200)))
            item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)

            let verticalGroup = NSCollectionLayoutGroup.vertical(
                layoutSize: NSCollectionLayoutSize(widthDimension: .absolute(200),
                                                   heightDimension: .absolute(400)),
                subitem: item,
                count: 2)

            let horizontalGroup = NSCollectionLayoutGroup.horizontal(
                layoutSize: NSCollectionLayoutSize(widthDimension: .absolute(200),
                                                   heightDimension: .absolute(400)),
                subitem: verticalGroup,
                count: 1)

            let section = NSCollectionLayoutSection(group: horizontalGroup)
            section.orthogonalScrollingBehavior = .continuous
            return section
        default:
            let item = NSCollectionLayoutItem(
                layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                   heightDimension: .fractionalHeight(1.0)))
            item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)

            let group = NSCollectionLayoutGroup.vertical(
                layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                   heightDimension: .fractionalHeight(1.0)),
                subitem: item,
                count: 1)
            let section = NSCollectionLayoutSection(group: group)
            return section
        }
    }
    return layout
}

셀에 삭제 애니메이션과 같이 애니메이션 변화도 있도록 하기 위해선 Animated Data Source를 사용해야 한다. 기존 방법과는 다르게 SectionModelAnimatableSectionModelType를 채택해야 하고 ModelIndentifiableTypeEquatable 프로토콜을 채택해야한다.