RxDataSources 사용법

Posted on 2021-09-23 by GKSRUDTN99
Swift&Xcode RxSwift

1. DataSource에 사용할 구조체 / 열거형들을 정의한다.

DataSource는 model(섹션 이름)과 items로 이루어져있다.

model과 items에 사용할 객체를 정의한다.

enum MypageSectionType {
    case rental
    case product
    case payment
    case option
}

enum MypageMenuType { 
    case rentalHistory
    case settlement
    case purchaseHistory
    case myProduct
    case suggestion
    case point
    case customerService
    case setting

    var title : String {
    switch self {
    case .myProduct: return "나의 물건함"
    case .point: return "꾿포인트"
    case .purchaseHistory: return "나의 예약"
    case .rentalHistory: return "이웃의 예약"
    case .settlement: return "꾸다 수익"
    case .suggestion: return "요청한 물건"
    case .customerService: return "고객센터"
    case .setting: return "설정"
    }
  }
}

2. Reactor 안에 Section의 형태를 정의한다.

typealias를 사용해 정의한다.
typealias Section = SectionModel<MypageSectionType, MypageMenuType>

3. VC에 DataSource의 형태를 정의한다.

typealias DataSource = RxTableViewSectionedReloadDataSource<MypageViewReactor.Section>

4. VC에 Cell의 형태를 정의하는 클로저를 정의한다.

StoryBoard에서 reuseableCell을 정의해 두어야 한다!

Cell 클래스를 따로 지정하고 싶다면, dequeue 한 뒤에 as?로 해당 Cell 클래스로 변환해주고,
해당 Cell 클래스 내에 정의된 setCell를 cell.setCell(item)과 같은 형태로 호출하여 설정하면 된다.
아래 tableview 자리에는 RxDatasource를 사용할 IBOutlet 객체가 들어가야한다.

private var configureCell: DataSource.ConfigureCell { return {
    dataSource, tableView, indexPath, item -> UITableViewCell in
    guard let cell = tableView.dequeueReusableCell(withIdentifier: "menuCell") else { return UITableViewCell() }
    (cell.viewWithTag(1) as? UILabel)?.text = item.title

    return cell
}}

5. VC에 DataSource 객체를 생성한다.

private lazy var itemsDataSource = DataSource { 
    configureCell: self.configureCell
}

6. 위의 두 과정을 바탕으로 아래의 Custom이 가능하다.

다른 Custom은 tableView나 CollectionView의 Delegate를 활용해야 한다.

dataSource.titleForHeaderInSection = ( dataSource, index in
  return dataSource.sectionModels[index].header
)

dataSource.titleForFooterInSection = { dataSource, index in
  return dataSource.sectionModels[index].footer
}

dataSource.canEditRowAtIndexPath = { dataSource, indexPath in
  return true
}

dataSource.canMoveRowAtIndexPath = { dataSource, indexPath in
  return true
}

7. section은 tableView/CollectionView의 Datasource에 바인딩한다.

State에 sections를 정의하고 VC에서 sections를 감지해 바인딩하기
class MypageViewController {

    reactor.state.map { $0.sections }
            .observeOn(MainScheduler.instance)
            .distinctUntilChanged()
            .bind(to: tableView.rx.items(dataSource: itemsDataSource))
            .disposed(by: disposeBag)

}

class MypageViewReactor { 
    struct State {
        let sections = [
            Section(model: .rental, items: [.rentalHistory, .purchaseHistory]), 
            Section(model: .product, items: [.myProduct, .suggestion]), 
            Section(model: .payment, items: [.settlement, .point]), 
            Section(model: .option, items: [.customerService, .setting])
        ]
    }
}
State에 products를 정의하고, products를 감지해 Section을 만들기
class VC {
    reactor.state.map { $0.products }
            .observeOn(MainScheduler.instance)
            .distinctUntilChanged()
            .compactMap { $0.data?.data }
            .map { [Reactor.Section(model: "", items: $0)] }
            .bind(to: self.collectionView.rx.items(dataSource: itemsDataSource))
            .disposed(by: disposeBag)
}

class Reactor { 
    struct State {
        var product = RevisionedData<Product>(data:nil)
    }
}

8. 해당 item의 Action 정의하기

Action Binding

IndexPath로 binding하기

collectionView.rx.itemSelected
      .map { indexPath in Reactor.Action.moveProductDetail(indexPath.item) }
      .bind(to: reactor.action )
      .disposed(by: disposeBag)

객체로 바인딩하기

tableView.rx.modelSelected(MypageMenuType.self)
      .map { type in Reactor.Action.actionMenu(type) }
      .bind(to: reactor.action)
      .disposed(by: disposeBag)