RxDataSource

김윤홍·2025년 1월 13일
post-thumbnail

RxDataSource란?

RxDataSource
rxDataSource는 UITableView와 UICollectionView DataSources를 RxSwift와 함께 사용되는 라이브러리이다.


RxDataSource를 사용하는 이유?

table, CollectionView를 DataSource를 이용해 구현을 하게 되면 간단한 기능이더라도 많은 수의 delegate 메서드들이 필요해 복잡하고 귀찮아진다.

특히 복잡한 데이터관리와 UI와 데이터 동기화, 비동기 데이터 처리부분에서 효율적이다.

이런점들을 RxSwift가 도움을 준다.


RxDataSource를 사용하는 방법

  1. Observable 시퀀스에 Data들을 보낸다.
  2. tableView, CollectionView에 data를 바인딩을 하면 된다.
let data = Observable<[String]>.just(["first element", "second element", "third element"])

data.bind(to: tableView.rx.items(cellIdentifier: "Cell")) { index, model, cell in
  cell.textLabel?.text = model
}
.disposed(by: disposeBag)

//data들을 바인딩하는 방법들
//rx.items(dataSource:protocol<RxTableViewDataSourceType, UITableViewDataSource>)
//rx.items(cellIdentifier:String)
//rx.items(cellIdentifier:String:Cell.Type:_:)
//rx.items(_:_:)

이러한 방법들은 간단한 데이터를 사용할 때는 좋지만 만약 복잡한데이터나, 두 개 이상의 섹션들을 다뤄야 할때는 어려움을 겪을 수 있고 애니메이션구현시도 마찬가지이다.(추가, 수정, 삭제등)

복잡한 데이터를 이용할 때의 경우를 살표보겠습니다.

let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, Int>>(configureCell: configureCell)
Observable.just([SectionModel(model: "title", items: [1, 2, 3])])
    .bind(to: tableView.rx.items(dataSource: dataSource))
    .disposed(by: disposeBag)

위의 코드를 살표보면 먼저 dataSource를 정의하고(타입 등) cell의 내용을 구성하는 것을 확인할 수 있습니다.

그 후 Observable에 SectionModel에 맞게 데이터를 보내고 이를 바인딩 하는 것을 확인할 수 있습니다.

그럼 데이터 구조는 어떻게 구성할 수 있나?

  1. 먼저 자신의 커스텀데이터를 구성합니다. tableView등에 들어갈 내용들을 구성하면 됩니다.
  2. 그 후 SectionViewModel을 채택해 section에 들어갈 내용들을 정의합니다.
  3. Item의 typealias를 정의합니다.
  4. Item의 배열타입으로 Item 프로퍼티를 선언합니다.
  5. 그리고 타입이 SectionOfCustomData인 dataSource를 만듭니다.

typealias는 새로운 타입을 만드는 것이 아닌 존재하는 타입에 대해 별칭을 프로그램에 알려주는 것입니다.

typealias StringDictionary<Value> = Dictionary<String, Value>
var aliasDictionary: StringDictionary<String> = [:]
var originDictionary: Dictionary<String, String> = [:]
//alias와 origin 딕셔너리는 실제로 같습니다.
struct CustomData {
  var anInt: Int
  var aString: String
  var aCGPoint: CGPoint
}

struct SectionOfCustomData {
  var header: String    
  var items: [Item]
}
extension SectionOfCustomData: SectionModelType {
  typealias Item = CustomData

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

//5번에 대한 내용
let dataSource = RxTableViewSectionedReloadDataSource<SectionOfCustomData>(
  configureCell: { dataSource, tableView, indexPath, item in
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
    cell.textLabel?.text = "Item \(item.anInt): \(item.aString) - \(item.aCGPoint.x):\(item.aCGPoint.y)"
    return cell
})

헤더와 푸터 그리고 섹션

위 처럼 만든 dataSource 클로저에 커스터마이징을 할 수 있다.

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
}

섹션을 정의하는 부분도 Observable에 section을 보내면 된다.

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
}

애니메이션 추가하기

dataSource에 애니메이션 기능을 이용하기 위해서는 SectionModelType대신 AnimatableSectionModelType프로토콜을 채택해야한다.

그리고 두개의 프로토콜을 추가적으로 채택을 해야하는데

첫번째는 IdentifiableType이다
IdentifiableType doc

IdentifiableType은 모델 인스턴스를 고유의 식별자로 나타내는 프로토콜이다.(UUID같은..)

두번째는 Equatable이다
Equatable doc
Equatable을 채택하면 rxDataSource가 어떤 cell에 변화가 있었고 그 특정셀에 애니메이션이 작동될수 있도록 도와준다.

특정 셀에 애니메이션을 보여주기 위해서 Equatable과 IdentifiableType을 채택한다.


0개의 댓글