๐Ÿ’กWhy




๊ธฐ์กด์˜ table๊ณผ collection view์—์„œ์˜ ๋ฐฉ๋ฒ•๋“ค๋ณด๋‹ค ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š” delegate method๋“ค์ด ๋งŽ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

RxSwif๋Š” ๊ฐ„๋‹จํ•œ data binding์„ ํ†ตํ•ด ์ง์„ ๋œ์–ด์ค๋‹ˆ๋‹ค.

  1. data๋ฅผ Observable sequence๋กœ ๋ฐ”๊ฟ”์ค๋‹ˆ๋‹ค.
  2. ๋‹ค์Œ์„ ์‚ฌ์šฉํ•ด์„œ data๋ฅผ bind ํ•ฉ๋‹ˆ๋‹ค
    • rx.items(dataSource:protocol<RxTableViewDataSourceType, UITableViewDataSource>)
    • rx.items(cellIdentifier:String)
    • rx.items(cellIdentifier:String:Cell.Type:_:)
    • rx.items(::)
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 ์„ธํŠธ์—์„œ๋Š” ์ž˜ ์ž‘๋™ํ•˜์ง€๋งŒ section์ด ๋งŽ์€ ๋ณต์žกํ•œ data set์ธ ๊ฒฝ์šฐ, adding/modifying/deleting ํ• ๋•Œ animation ์ˆ˜ํ–‰์ด ํ•„์š”ํ• ๋•Œ ์ž˜ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.



## ๐Ÿ’กHow


๋‹ค์Œ data structrue๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
struct CustomData {
  var anInt: Int
  var aString: String
  var aCGPoint: CGPoint
}
  1. SectionModelType protocol์„ ๋”ฐ๋ฅด๋Š” struct๋กœ section์„ ์ •์˜ํ•œ๋‹ค.
struct SectionOfCustomData {
  var header: String    
  var items: [Item]
}
extension SectionOfCustomData: SectionModelType {
  typealias Item = CustomData

   init(original: SectionOfCustomData, items: [Item]) {
    self = original
    self.items = items
  }
}
  1. dataSource ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค๊ณ  SectionOfCustomData ํƒ€์ž…์„ ์ „๋‹ฌํ•œ๋‹ค.
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
})
  1. ํ•„์š”์—๋”ฐ๋ผ dataSource์˜ closure๋ฅผ ์ปค์Šคํ…€ํ•ฉ๋‹ˆ๋‹ค.
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
}
  1. CustomData์˜ Observable sequence๋กœ ์‹ค์ œ data๋ฅผ ์ •์˜ํ•œ๋‹ค.
let sections = [
  SectionOfCustomData(header: "First section", items: [CustomData(anInt: 0, aString: "zero", aCGPoint: CGPoint.zero), CustomData(anInt: 1, aString: "one", aCGPoint: CGPoint(x: 1, y: 1)) ]),
  SectionOfCustomData(header: "Second section", items: [CustomData(anInt: 2, aString: "two", aCGPoint: CGPoint(x: 2, y: 2)), CustomData(anInt: 3, aString: "three", aCGPoint: CGPoint(x: 3, y: 3)) ])
]

Observable.just(sections)
  .bind(to: tableView.rx.items(dataSource: dataSource))
  .disposed(by: disposeBag)

RxdataSoureces๋Š” ์ž๋™์œผ๋กœ animating ๋ณ€ํ™”๋ฅผ ๊ด€๋ฆฌํ•ด์ฃผ๋Š” ๋‘๊ฐ€์ง€ data source ํƒ€์ž…์ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • RxTableViewSectionedAnimatedDataSource
  • RxCollectionViewSectionedAnimatedDataSource

2๊ฐœ์˜ data source์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋ช‡ ๊ฐ€์ง€ ์ถ”๊ฐ€ ๋‹จ๊ณ„๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

  • SectionOfCustomData๋Š” AnimatableSectionModelType๋ฅผ ์ค€์ˆ˜ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.
  • ๋‹น์‹ ์˜ data model์€ ๋‹ค์Œ์„ ์ค€์ˆ˜ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.
    • IdentifiableType: ํ”„๋กœํ† ์ฝœ์—์˜ํ•ด ์ œ๊ณต๋œ identity๋Š” ๊ทธ ๋ชจ๋ธ์˜ ์ธ์Šคํ„ด์Šค๋ฅผํ…์ŠคํŠธ ๋Œ€ํ‘œํ•˜๋Š” immutable(๋ณ€๊ฒฝ๋  ์ˆ˜ ์—†๋Š”) identifier์ž…๋‹ˆ๋‹ค.
    • Equatable: RxDataSources๊ฐ€ ์–ด๋–ค cell์ด ๋ณ€ํ™”ํ–ˆ๋Š”์ง€ ๊ฒฐ์ •ํ•˜๋Š” ๊ฒƒ์„ ๋„์™€์ค๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ด๋Ÿฐ ์…€๋“ค์ด animateํ•  ์ˆ˜ ์žˆ๊ฒŒํ•ฉ๋‹ˆ๋‹ค. Car model์˜ property์˜ ๋ณ€ํ™”๊ฐ€ ์…€์˜ animated reload๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.