I have a collectionview populated with data models. I am trying to update the bool property of the nested model when user taps on collectionview cell. In turn, the collectionview should reload and cell should be updated w.r.t to bool property. But the property changes in the model is not updating the collectionview.
//Model
struct MultiSelectionQuestionModel {
var header: String
var items: [Item]
}
extension MultiSelectionQuestionModel: SectionModelType {
typealias Item = MultiSelectionAnswerModel
init(original: MultiSelectionQuestionModel, items: [Item]) {
self = original
self.items = items
}
}
struct MultiSelectionAnswerModel {
var text: String
var isSelected: Bool = false //property to be updated
var cellType: CellType = .CustomType
}
//CollectionView methods
func populateCells() {
let dataSource = RxCollectionViewSectionedReloadDataSource
<MultiSelectionQuestionModel>(
configureCell: { (_, collectionView, indexPath, item) in
guard let cell = collectionView
.dequeueReusableCell(withReuseIdentifier: item.cellType.rawValue, for: indexPath) as? MultiSelectionBaseCell else {
return MultiSelectionBaseCell()
}
cell.configure(item: item)
return cell
})
//handle collectionview cell tap
collectionView.rx.itemSelected.asObservable().map { (indexPath) -> Result in
//This method is called to update `isSelected` property. Once `isSelected` is updated. I am expecting the collectionview to reload and update the cell.
self.viewModel.toggleItemSelected(indexPath: indexPath)
}
collectionView.rx.setDelegate(self).disposed(by: disposeBag)
viewModel.items
.bind(to: collectionView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
}
//ViewModel
struct MultiSelectionCollectionViewModel {
var items: BehaviorRelay<[MultiSelectionQuestionModel]> = BehaviorRelay(value: [])
var delegate:
init(questions: BehaviorRelay<[MultiSelectionQuestionModel]>) {
self.items = questions
}
//This method is called to update `isSelected` property. Once `isSelected` is updated. I am expecting the collectionview to reload and update the cell.
func toggleItemSelected(indexPath: IndexPath) {
let item = self.items.value[indexPath.section]
if let options = item.items as? [MultiSelectionAnswerModel] {
var optionItem = options[indexPath.row]
optionItem.isSelected = true // Collectionview reload Not working.
}
}
}
I just started learning RxSwift. Any help is appreciated. Thanks
You have to call items.accept(_:) to push a new array out of your BehaviorRelay. In order to do that, you have to build a new array. Also, BehaviorRelays (any Relays or Subjects) should never be vars; they should always be lets.
Also, keep in mind that you can't actually modify the array in the relay. Instead you replace it with a new array.
This should work:
struct MultiSelectionCollectionViewModel {
let items: BehaviorRelay<[MultiSelectionQuestionModel]>
init(questions: BehaviorRelay<[MultiSelectionQuestionModel]>) {
self.items = questions
}
//This method is called to update `isSelected` property. Once `isSelected` is updated. I am expecting the collectionview to reload and update the cell.
func toggleItemSelected(indexPath: IndexPath) {
var multiSelectionQuestionModel = items.value // makes a copy of the array contained in `items`.
var item = multiSelectionQuestionModel[indexPath.section].items[indexPath.row] // makes a copy of the item to be modified
item.isSelected = true // modifies the item copy
multiSelectionQuestionModel[indexPath.section].items[indexPath.row] = item // modifies the copy of items by replacing the old item with the new one
items.accept(multiSelectionQuestionModel) // tells BehaviorRelay to update with the new array of items (it will emit the new array to all subscribers.)
}
}
protocol SectionModelType { }
enum CellType {
case CustomType
}
struct MultiSelectionQuestionModel {
var header: String
var items: [Item]
}
extension MultiSelectionQuestionModel: SectionModelType {
typealias Item = MultiSelectionAnswerModel
init(original: MultiSelectionQuestionModel, items: [Item]) {
self = original
self.items = items
}
}
struct MultiSelectionAnswerModel {
var text: String
var isSelected: Bool = false //property to be updated
var cellType: CellType = .CustomType
}
Related
I'm making a Twitter clone app.
If I press the like button on the posts in each cell, I want to reflect the number of likes to increase. Also, I want to make it reflect in the cell as soon as I delete or modify the post. There is also a video player in the cell like Twitter, but every time I press the like button, the ViewModel accepts again, which causes the video that was running to turn off. Please tell me the best way to implement the Like button...
Cell.swift
func bind(_ viewModel: PostItemViewModel) {
.....
likesLabel.text = String(viewModel.likes)
....
}
....
likesBtn.rx.tapGesture().when(.ended).mapToVoid().asSignalOnErrorJustComplete()
.emit(onNext: { [unowned self] in
delegate?.pressLikesBtn(tag)
}).disposed(by: disposeBag)
ViewController.swift
class PostsViewController: UIViewController {
private let pressLikesRelay = PublishRelay<Int>()
private let deleteRelay = PublishRelay<Int>()
private let disposeBag = DisposeBag()
var viewModel: PostsViewModel!
.......
.......
private func bindViewModel() {
assert(viewModel != nil)
let viewWillAppear = rx.viewWillAppear
.mapToVoid()
.asSignalOnErrorJustComplete()
let pull = timeLineTableView.refreshControl!.rx
.controlEvent(.valueChanged)
.asSignal()
let reachedBottom = timeLineTableView.rx.reachedBottom().asSignal()
let input = PostsViewModel.Input(fetchInitial: Signal.merge(viewWillAppear, pull), fetchNext: reachedBottom, likesTrigger: pressLikesRelay.asSignal(), deleteTrigger: deleteRelay.asSignal())
let output = viewModel.transform(input: input)
output.posts.drive(timeLineTableView.rx.items(cellIdentifier: PostTableViewCell.identifier, cellType: PostTableViewCell.self)) {
row, item, cell in
cell.delegate = self
cell.bind(item)
cell.tag = row
}.disposed(by: disposeBag)
}
extension PostsViewController: PostTableCellDelegate {
func pressLikesBtn(_ index: Int) {
pressRelay.accept(index)
}
func deletePost(_ index: Int) {
deleteRelay.accept(index)
}
}
ViewModel.swift
final class PostsViewModel: ViewModelType {
...
private let postsRelay = BehaviorRelay<[PostItemViewModel]>(value: [])
.....
func transform(input: Input) -> Output {
.......
input.likesTrigger.emit(onNext: { [unowned self] index in
var updated = self.postsRelay.value
updated[index].plusLikes()
self.postsRelay.accept(updated)
}).disposed(by: disposeBag)
input.deleteTrigger.emit(onNext: { [unowned self] index in
var deleted = self.postsRelay.value
deleted.remove(at: index)
self.postsRelay.accept(deleted)
}).disposed(by: disposeBag)
return Output(
...,
posts: postsRelay.asDriver(),
...)
}
}
PostItemViewModel.swift
public struct PostItemViewModel {
.....
var likes: Int
....
mutating func plusLikes() {
self.vote1 +=1
}
}
Here is a sample app that shows how to do what you want. RxMultiCounter.
The key is to only update the table view when you add or remove cells. Each cell needs to independently observe the state and when the inner state of the cell changes, the cell updates directly.
I am trying to implement a tableView by using RxTableViewSectionedAnimatedDataSource, I set all the subclasses correct, when I try to bind dataSource to my tableView, the compiler keeps warning me
Instance method 'items(dataSource:)' requires that 'TableViewSectionedDataSource' conform to 'RxTableViewDataSourceType'
Here is the code
let tableView = UITableView()
let dataSource = RxTableViewSectionedAnimatedDataSource<CustomSectionDataType>(configureCell: { dataSource, tableView, indexPath, item in
return UITableViewCell()
})
dataSource.titleForHeaderInSection = { (ds, section) in
let sectionModel = ds.sectionModels[section]
return sectionModel.header
}
let sectionDatas = [CustomSectionDataType(ID: "1", header: "test", items: ["WTF!"])]
let items = BehaviorRelay(value: [sectionDatas])
items
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: self.disposeBag)
Custom Section Class
struct CustomSectionDataType {
var ID: String
var header: String
var items: [Item]
}
extension CustomSectionDataType: AnimatableSectionModelType {
typealias Item = String
typealias Identity = String
var identity: String {
return ID
}
init(original: CustomSectionDataType, items: [Item]) {
self = original
self.items = items
}
}
You have a too deeply nested array for your items type.
The type is defined as BehaviorRelay<[[CustomSectionDataType]]> when it should be BehaviorRelay<[CustomSectionDataType]>.
Also, consider using a typealias instead of creating your own custom type:
typealias CustomSectionDataType = AnimatableSectionModel<String, String>
or if you have two headers that are the same but with different IDs then:
struct CustomModel: IdentifiableType {
var identity: String
var header: String
}
typealias CustomSectionDataType = AnimatableSectionModel<CustomModel, String>
It makes life a bit easier.
I am trying to create a Diffable tableView who's cells can be updated (info and layout) by the user (ie- hiding or showing a label or image that holds an optional property) in a separate editing tableview attached to the cell through a segue. I started by adding a backingStore property who's values are a custom ListItem class. The class is hashable with a UUID() as one of the properties.
class ListDrillDownTableViewController: UITableViewController {
fileprivate var dataSource: ListDrillDownDataSource!
fileprivate var currentSnapshot: ListDrillDownSnaphot?
var list: List = List(name: "Test List", listStyle: .preSetStyle[0], listItems: [], users: [])
var backingStore = [UUID: ListItem]()
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = createDataSource()
updateUI(animated: false)
}
Then I created a method to create the dataSource giving the cell provider the UUID to look up the listItem. I also updated the UI with the initial snapshot populating the UUIDs and the backingStore.
fileprivate func createDataSource() -> ListDrillDownDataSource {
let dataSource = ListDrillDownDataSource(tableView: tableView) { (tableView, indexPath, uuid) -> UITableViewCell? in
let cell = tableView.dequeueReusableCell(withIdentifier: "ListItemCell", for: indexPath) as! ListItemCell
guard let listItem = self.backingStore[uuid] else { return cell }
cell.titleLabel.text = listItem.title
if let cost = listItem.cost {
cell.costLabel.isHidden = false
cell.costLabel.text = (listItem.costString(cost: cost))
} else {
cell.costLabel.isHidden = true
}
if listItem.note == "" {
cell.noteIcon.isHidden = true
} else {
cell.noteIcon.isHidden = false
}
return cell
}
self.tableView.dataSource = dataSource
return dataSource
}
func updateUI(animated: Bool = true) {
var snapshot = ListDrillDownSnaphot()
snapshot.appendSections(["main"])
let uuids = self.list.listItems.map { _ in UUID() }
snapshot.appendItems(uuids)
for (uuid, listItem) in zip(uuids, list.listItems) {
self.backingStore[uuid] = listItem
}
self.dataSource.apply(snapshot, animatingDifferences: animated)
}
Finally, when the user taps a cell, makes there edits and taps done the view unwinds back to this controller and I attempt to update the dataSource in the unwind method. As it stands the app will create the first new cell but when a second is attempted it reloads the first row twice and as I keep adding it doubles the existing rows. I'm guessing because I'm appending the whole list over and over? I can't figure out how to access JUST the appended uuid.
When I tap into an existing cell and edit the info it works, but if I go in again to the row and come out the cell goes back to it's original state. If I keep trying it bounces back and forth between the original state and the updated, almost like it is bouncing back and forth between snapshots? I feel like I'm missing something obvious but I can't seem to figure it out. Any thoughts?
#IBAction func unwindToListDrillDownTableView(_ unwindSegue: UIStoryboardSegue) {
guard unwindSegue.identifier == "DoneAddEditListItemUnwind" else { return }
let sourceViewController = unwindSegue.source as! ListItemDetailTableViewController
self.list.listStyle?.categories = sourceViewController.currentCategories
if let listItem = sourceViewController.listItem {
if let indexOfExistingListItem = list.listItems.firstIndex(of: listItem) {
// If existing, update the ListItem and then the view.
list.listItems[indexOfExistingListItem] = listItem
let uuid = self.dataSource.itemIdentifier(for: IndexPath(row: indexOfExistingListItem, section: 0))
let uuids = self.list.listItems.map { _ in UUID() }
var snapshot = self.dataSource.snapshot()
snapshot.reloadItems([uuid!])
for (uuid, listItem) in zip(uuids, list.listItems) {
self.backingStore[uuid] = listItem
}
self.dataSource.apply(snapshot, animatingDifferences: true)
} else {
list.listItems.append(listItem)
if self.backingStore.keys.isEmpty {
updateUI()
} else {
var snapshot = self.dataSource.snapshot()
let uuids = self.list.listItems.map { _ in UUID() }
snapshot.reloadSections(["main"])
snapshot.appendItems(uuids)
for (uuid, listItem) in zip(uuids, list.listItems) {
self.backingStore[uuid] = listItem
}
self.dataSource.apply(snapshot, animatingDifferences: true)
}
}
}
}
I am trying to understand how to implement MVVM with a list of objects and an UICollectionView. I am not understanding how to implement the User iteration -> Model flow.
I have setup a test application, the Model is just a class with an Int, and the View is an UICollectionViewCell which shows a text with the corresponding Int value and have plus, minus and delete buttons to increment, decrease and remove a element respectively.
Each entry looks like:
I would like to know the best way to use MVVM and RxSwift the update/remove a cell.
I have a list random generated Int values
let items: [Model]
the Model which just have the Int value
class Model {
var number: Int
init(_ n: Int = 0) {
self.number = n
}
}
The ViewModel class which just hold the Model and has an Observable
class ViewModel {
var value: Observable<Model>
init(_ model: Model) {
self.value = Observable.just(model)
}
}
And the Cell
class Cell : UICollectionViewCell {
class var identifier: String { return "\(self)" }
var bag = DisposeBag()
let label: UILabel
let plus: UIButton
let minus: UIButton
let delete: UIButton
....
var viewModel: ViewModel? = nil {
didSet {
....
viewModel.value
.map({ "number is \($0.number)" })
.asDriver(onErrorJustReturn: "")
.drive(self.label.rx.text)
.disposed(by: self.bag)
....
}
}
}
What I don't understand clearly how to do is how to connect the buttons to the corresponding action, update the model and the view afterwards.
Is the Cell's ViewModel responsible for this? Should it be the one receiving the tap event, updating the Model and then the view?
In the remove case, the cell's delete button needs to remove the current Model from the data list. How can this be done without mixing everything all together?
Here is the project with the updates below in GitHub: https://github.com/dtartaglia/RxCollectionViewTester
The first thing we do is to outline all our inputs and outputs. The outputs should be members of the view model struct and the inputs should be members of an input struct.
In this case, we have three inputs from the cell:
struct CellInput {
let plus: Observable<Void>
let minus: Observable<Void>
let delete: Observable<Void>
}
One output for the cell itself (the label) and two outputs for the cell's parent (presumably the view controller's view model.)
struct CellViewModel {
let label: Observable<String>
let value: Observable<Int>
let delete: Observable<Void>
}
Also we need to setup the cell to accept a factory function so it can create a view model instance. The cell also needs to be able to reset itself:
class Cell : UICollectionViewCell {
var bag = DisposeBag()
var label: UILabel!
var plus: UIButton!
var minus: UIButton!
var delete: UIButton!
// code to configure UIProperties omitted.
override func prepareForReuse() {
super.prepareForReuse()
bag = DisposeBag() // this resets the cell's bindings
}
func configure(with factory: #escaping (CellInput) -> CellViewModel) {
// create the input object
let input = CellInput(
plus: plus.rx.tap.asObservable(),
minus: minus.rx.tap.asObservable(),
delete: delete.rx.tap.asObservable()
)
// create the view model from the factory
let viewModel = factory(input)
// bind the view model's label property to the label
viewModel.label
.bind(to: label.rx.text)
.disposed(by: bag)
}
}
Now we need to build the view model's init method. This is where all the real work happens.
extension CellViewModel {
init(_ input: CellInput, initialValue: Int) {
let add = input.plus.map { 1 } // plus adds one to the value
let subtract = input.minus.map { -1 } // minus subtracts one
value = Observable.merge(add, subtract)
.scan(initialValue, accumulator: +) // the logic is here
label = value
.startWith(initialValue)
.map { "number is \($0)" } // create the string from the value
delete = input.delete // delete is just a passthrough in this case
}
}
You will notice that the view model's init method needs more than what is provided by the factory function. The extra info will be provided by the view controller when it creates the factory.
The view controller will have this in its viewDidLoad:
viewModel.counters
.bind(to: collectionView.rx.items(cellIdentifier: "Cell", cellType: Cell.self)) { index, element, cell in
cell.configure(with: { input in
let vm = CellViewModel(input, initialValue: element.value)
// Remember the value property tracks the current value of the counter
vm.value
.map { (id: element.id, value: $0) } // tell the main view model which counter's value this is
.bind(to: values)
.disposed(by: cell.bag)
vm.delete
.map { element.id } // tell the main view model which counter should be deleted
.bind(to: deletes)
.disposed(by: cell.bag)
return vm // hand the cell view model to the cell
})
}
.disposed(by: bag)
For the above example I assume that:
counters is of type Observable<[(id: UUID, value: Int)]> and comes from the view controller's view model.
values is of type PublishSubject<(id: UUID, value: Int)> and is input into the view controller's view model.
deletes is of type PublishSubject<UUID> and is input into the view controller's view model.
The construction of the view controller's view model follows the same pattern as the one for the cell:
Inputs:
struct Input {
let value: Observable<(id: UUID, value: Int)>
let add: Observable<Void>
let delete: Observable<UUID>
}
Outputs:
struct ViewModel {
let counters: Observable<[(id: UUID, value: Int)]>
}
Logic:
extension ViewModel {
private enum Action {
case add
case value(id: UUID, value: Int)
case delete(id: UUID)
}
init(_ input: Input, initialValues: [(id: UUID, value: Int)]) {
let addAction = input.add.map { Action.add }
let valueAction = input.value.map(Action.value)
let deleteAction = input.delete.map(Action.delete)
counters = Observable.merge(addAction, valueAction, deleteAction)
.scan(into: initialValues) { model, new in
switch new {
case .add:
model.append((id: UUID(), value: 0))
case .value(let id, let value):
if let index = model.index(where: { $0.id == id }) {
model[index].value = value
}
case .delete(let id):
if let index = model.index(where: { $0.id == id }) {
model.remove(at: index)
}
}
}
}
}
I'm doing it this way:
ViewModel.swift
import Foundation
import RxSwift
import RxCocoa
typealias Model = (String, Int)
class ViewModel {
let disposeBag = DisposeBag()
let items = BehaviorRelay<[Model]>(value: [])
let add = PublishSubject<Model>()
let remove = PublishSubject<Model>()
let addRandom = PublishSubject<()>()
init() {
addRandom
.map { _ in (UUID().uuidString, Int.random(in: 0 ..< 10)) }
.bind(to: add)
.disposed(by: disposeBag)
add.map { newItem in self.items.value + [newItem] }
.bind(to: items)
.disposed(by: disposeBag)
remove.map { removedItem in
self.items.value.filter { (name, _) -> Bool in
name != removedItem.0
}
}
.bind(to: items)
.disposed(by: disposeBag)
}
}
Cell.swift
import Foundation
import Material
import RxSwift
import SnapKit
class Cell: Material.TableViewCell {
var disposeBag: DisposeBag?
let nameLabel = UILabel(frame: .zero)
let valueLabel = UILabel(frame: .zero)
let removeButton = FlatButton(title: "REMOVE")
var model: Model? = nil {
didSet {
guard let (name, value) = model else {
nameLabel.text = ""
valueLabel.text = ""
return
}
nameLabel.text = name
valueLabel.text = "\(value)"
}
}
override func prepare() {
super.prepare()
let textWrapper = UIStackView()
textWrapper.axis = .vertical
textWrapper.distribution = .fill
textWrapper.alignment = .fill
textWrapper.spacing = 8
nameLabel.font = UIFont.boldSystemFont(ofSize: 24)
textWrapper.addArrangedSubview(nameLabel)
textWrapper.addArrangedSubview(valueLabel)
let wrapper = UIStackView()
wrapper.axis = .horizontal
wrapper.distribution = .fill
wrapper.alignment = .fill
wrapper.spacing = 8
addSubview(wrapper)
wrapper.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(8)
}
wrapper.addArrangedSubview(textWrapper)
wrapper.addArrangedSubview(removeButton)
}
}
ViewController.swift
import UIKit
import Material
import RxSwift
import SnapKit
class ViewController: Material.ViewController {
let disposeBag = DisposeBag()
let vm = ViewModel()
let tableView = UITableView()
let addButton = FABButton(image: Icon.cm.add, tintColor: .white)
override func prepare() {
super.prepare()
view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
addButton.pulseColor = .white
addButton.backgroundColor = Color.red.base
view.layout(addButton)
.width(48)
.height(48)
.bottomRight(bottom: 16, right: 16)
addButton.rx.tap
.bind(to: vm.addRandom)
.disposed(by: disposeBag)
tableView.register(Cell.self, forCellReuseIdentifier: "Cell")
vm.items
.bind(to: tableView.rx.items) { (tableView, row, model) in
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! Cell
cell.model = model
cell.disposeBag = DisposeBag()
cell.removeButton.rx.tap
.map { _ in model }
.bind(to: self.vm.remove)
.disposed(by: cell.disposeBag!)
return cell
}
.disposed(by: disposeBag)
}
}
Note that a common mistake is creating the DisposeBag inside Cell only once, this will causing confusing when the action got triggered.
The DisposeBag must be re-created every time the Cell got reused.
A complete working example can be found here.
I have a very simple project, where I want to dynamically filter content in UITableView regarding pressed index in UISegmentedControl. I'm using MVVM with RxSwift, Realm and RxDataSources. So my problem, that if I want to update content in UITableView I need to create 'special' DisposeBag, only for that purposes, and on each selection in UISegmentedControl nil it and create again. Only in this case, if I'm understand right, subscription is re-newed, and UITableView displays new results from Realm.
So is there any better way to do such operation? Without subscribing every time, when I switch tab in UISegmentedControl. Here's my code:
//ViewController
class MyViewController : UIViewController {
//MARK: - Props
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var segmentedControl: UISegmentedControl!
let dataSource = RxTableViewSectionedReloadDataSource<ItemsSection>()
let disposeBag = DisposeBag()
var tableViewBag: DisposeBag!
var viewModel: MyViewModel = MyViewModel()
//MARK: - View lifecycle
override func viewDidLoad() {
super.viewDidLoad()
self.setupRxTableView()
}
//MARK: - Setup observables
fileprivate func setupRxTableView() {
dataSource.configureCell = { ds, tv, ip, item in
let cell = tv.dequeueReusableCell(withIdentifier: "ItemCell") as! ItemTableViewCell
return cell
}
bindDataSource()
segmentedControl.rx.value.asDriver()
.drive(onNext: {[weak self] index in
guard let sSelf = self else { return }
switch index {
case 1:
sSelf.bindDataSource(filter: .active)
case 2:
sSelf.bindDataSource(filter: .groups)
default:
sSelf.bindDataSource()
}
}).disposed(by: disposeBag)
}
private func bindDataSource(filter: Filter = .all) {
tableViewBag = nil
tableViewBag = DisposeBag()
viewModel.populateApplying(filter: filter)
}).bind(to: self.tableView.rx.items(dataSource: dataSource))
.disposed(by: tableViewBag)
}
}
//ViewModel
class MyViewModel {
func populateApplying(filter: Filter) -> Observable<[ItemsSection]> {
return Observable.create { [weak self] observable -> Disposable in
guard let sSelf = self else { return Disposables.create() }
let realm = try! Realm()
var items = realm.objects(Item.self).sorted(byKeyPath: "date", ascending: false)
if let predicate = filter.makePredicate() { items = items.filter(predicate) }
let section = [ItemsSection(model: "", items: Array(items))]
observable.onNext(section)
sSelf.itemsToken = items.addNotificationBlock { changes in
switch changes {
case .update(_, _, _, _):
let section = [ItemsSection(model: "", items: Array(items))]
observable.onNext(section)
default: break
}
}
return Disposables.create()
}
}
}
Don't recall if this is breaking MVVM off the top of my head, but would Variable not be what you're looking for?
Variable<[TableData]> data = new Variable<[TableData]>([])
func applyFilter(filter: Predicate){
data.value = items.filter(predicate) //Any change to to the value will cause the table to reload
}
and somewhere in the viewController
viewModel.data.rx.asDriver().drive
(tableView.rx.items(cellIdentifier: "ItemCell", cellType: ItemTableViewCell.self))
{ row, data, cell in
//initialize cells with data
}