The Pragmatic Ball boy

iOSを中心にやってる万年球拾いの老害エンジニアメモ

RxSwiftでTableView その3

RxTableViewSectionedReloadDataSourceでは複数Sectionに対応できましたが、 Section単位でreloadData()がされてしまっていました。

今回はRxTableViewSectionedAnimatedDataSourceを使ってみます

RxTableViewSectionedAnimatedDataSource

ViewControllerはRxTableViewSectionedReloadDataSourceとほぼ一緒で、dataSourceの型がRxTableViewSectionedAnimatedDataSourceになっただけです。

ViewController

import RxSwift
import RxCocoa
import RxDataSources

class MultiListViewController: UIViewController {
  
  @IBOutlet weak var tableView: UITableView!
  @IBOutlet weak var button1: UIButton!

  private let viewModel = MultiListViewModel()

  private let dataSource = RxTableViewSectionedAnimatedDataSource<MySectionModel>()
  private let disposeBag = DisposeBag()

  override func viewDidLoad() {
    dataSource.configureCell = { (_, tableView, indexPath, element) in
      let cell = tableView.dequeueReusableCellWithIdentifier("Cell1")!
      cell.textLabel?.text = "\(element) @ row \(indexPath.row)"
      return cell
    }

    viewModel.items.asObservable()
      .bindTo(tableView.rx_itemsWithDataSource(dataSource))
      .addDisposableTo(disposeBag)

    button1.rx_tap
      .bindNext { [weak self] in
      self?.viewModel.change()
    }
    .addDisposableTo(disposeBag)
  }
}

ViewModel

dataSourceとbindingさせる配列はAnimatableSectionModelType protocolに対応している必要があります。

更にAnimatableSectionModelTypeのitemはIdentificableType protocolに対応している必要があります。これはitemが変化したかどうか判定するために使われます。

struct MySectionModel {
  var header: String
  var items: [Item]
}

extension MySectionModel : AnimatableSectionModelType {
  typealias Item = String

  var identity: String {
    return header
  }

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

extension String: IdentifiableType {
  public typealias Identity = String

  public var identity : Identity {
    return self
  }
}

class MultiListViewModel {

  let items = Variable<[MySectionModel]>([])

  var section1 = MySectionModel(header: "section1", items: ["a", "b", "c"])
  var section2 = MySectionModel(header: "section2", items: ["d", "e", "f"])

  init () {
    items.value.append(section1)
    items.value.append(section2)
  }

  func change() {
    items.value[0] = MySectionModel(header: "section1", items: ["a", "b", "c", "z"])
  }
}

変更があった場合、内部ではtableViewのperformBatchUpdatesが実行されるので変化のあったcellだけ変更がかかります。

アニメーションを変更したい場合はAnimationContigurationを変更してやればよいです。

    dataSource.animationConfiguration = AnimationConfiguration(insertAnimation: .Right, reloadAnimation: .Right, deleteAnimation: .Right)

RxTableViewSectionedAnimatedDataSourceの使い所

  • cellの挿入や削除などでアニメーションを入れたい場合
  • 変化があった場合にSectionやTableView全体をreloadDataしたくない場合

ただ気になるところが、内部的な変化の判定するために、変更前後の配列の比較を行っているので効率的とはいえません。 利用する側にとっては何も考えずに配列ごと上書きしてしまえばよいのですが、すごい大きい配列になった場合にパフォーマンスとか大丈夫なのかなと懸念がありそうな気がします。