I can achieve this when not using RxSwift by doing the following. It works great, plays needed animation on the row being removed and doesn't reload the whole table:
let item = self.dataSource[(indexPath as NSIndexPath).row]
self.dataSource.remove(at: (indexPath as NSIndexPath).row)
tableView.deleteRows(at: [indexPath], with: .right)
With RxSwift I'm binding data to the tableView by doing this and when I accept new array, it reloads the whole table and the remove animation is not played:
// ViewModel:
let items: BehaviorRelay<[Item]> = BehaviorRelay(value: [])
func removeItem(_ item: Item) {
var loadedItems = items.value
if let removeIndex = loadedItems.firstIndex(of: item) {
loadedItems.remove(at: removeIndex)
}
items.accept(loadedItems)
}
//ViewController:
viewModel.items
.bind(to: tableView.rx.items) { [weak self] (table, index, item) in
// Creating cell for the `item`....
return cell
}
.disposed(by: self.disposeBag)
How can I update the viewModel.item
source to remove a single element and make sure that the remove animation is playing on the tableview?
There is a library called RxDataSources that implements this very thing. Or you can do it yourself if your needs are simple. Just implement the one required method.
Here's an example:
class Example: NSObject, RxTableViewDataSourceType, UITableViewDataSource {
private var items = [Item]()
func tableView(_ tableView: UITableView, observedEvent: RxSwift.Event<[Item]>) {
switch observedEvent {
case let .next(items):
// figure out which items have been added and which have been removed.
// animate the table view as usual.
// then:
self.items = items
default:
break
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// build a cell as usual.
}
}
You would use this by simply binding with it, like this:
func example(source: Observable<[Item]>, tableView: UITableView, disposeBag: DisposeBag) {
source
.bind(to: tableView.rx.items(dataSource: Example()))
.disposed(by: disposeBag)
}
You would add/remove cells by emitting a new array on the source
observable.
Here is a complete example using RxDataSources if you choose to use it:
typealias ItemSection = AnimatableSectionModel<String, Item>
func example(source: Observable<[ItemSection]>, tableView: UITableView, disposeBag: DisposeBag) {
let dataSource = RxTableViewSectionedAnimatedDataSource<ItemSection>(
animationConfiguration: AnimationConfiguration(deleteAnimation: .right), // since you specified right delete in your question
configureCell: { dataSource, tableView, indexPath, item in
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MyTableViewCell
cell.configure(with: item)
return cell
}
)
source
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
}
struct Item: IdentifiableType, Equatable {
let identity: Int
let name: String
}
final class MyTableViewCell: UITableViewCell {
func configure(with item: Item) {
self.textLabel?.text = item.name
}
}
Note: in neither case do you need to store a property of the data source, the RxCocoa library will take care of that for you.