I have a big tableView that can fit more than 100 cells, I want these cells to be draggable and it works fine with first 10 cells, but after that index I got this error. I use RxDataSources, I saw that someone wrote that stateMachine will help with this issue, but it didn't work for me. Here is my code:
import Foundation
import RxSwift
import RxDataSources
import SnapKit
import RxRelay
import RxCocoa
class BindableTableViewController<T: AnimatableSectionModelType>: UIViewController, UITableViewDelegate, UITableViewDragDelegate {
func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
let dragItem = UIDragItem(itemProvider: NSItemProvider())
dragItem.localObject = tableView.visibleCells[indexPath.row]
return [dragItem]
}
var items: RxSwift.Observable<[T]>?
var onItemSelected: ((IndexPath) -> Void)?
var onItemMoved: ((ItemMovedEvent) -> Void)?
var onItemRemoved: ((IndexPath) -> Void)?
var tableView: UITableView
var classesToRegister: [String: AnyClass]
weak var dataSource: RxTableViewSectionedAnimatedDataSource<T>?
var heightForRow: CGFloat
var supportsDragging: Bool
private let disposeBag = DisposeBag()
init(items: RxSwift.Observable<[T]>?,
heightForRow: CGFloat,
tableViewColor: UIColor = .clear,
onItemSelected: ((IndexPath) -> Void)? = nil,
onItemMoved: ((ItemMovedEvent) -> Void)? = nil,
onItemRemoved: ((IndexPath) -> Void)? = nil,
dataSource: RxTableViewSectionedAnimatedDataSource<T>,
classesToRegister: [String: AnyClass],
supportsDragging: Bool = false,
style: UITableView.Style? = .plain) {
self.heightForRow = heightForRow
self.items = items
self.dataSource = dataSource
self.classesToRegister = classesToRegister
self.onItemSelected = onItemSelected
self.onItemMoved = onItemMoved
self.onItemRemoved = onItemRemoved
self.supportsDragging = supportsDragging
tableView = UITableView(frame: .zero, style: style ?? .plain)
super.init(nibName: nil, bundle: nil)
tableView.backgroundColor = tableViewColor
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return heightForRow
}
override func viewDidLoad() {
super.viewDidLoad()
for (key, value) in classesToRegister {
tableView.register(value, forCellReuseIdentifier: key)
}
items?.bind(to: tableView.rx.items(dataSource: dataSource!))
.disposed(by: disposeBag)
items?.subscribe(onNext: { sections in
print(sections.first?.items.map({ $0.identity }))
}).disposed(by: disposeBag)
items?.takeLast(2).subscribe(onNext: { [weak self] sectionModels in
sectionModels.first?.items.forEach({ el in
print(el.identity)
})
let initialState = SectionedTableViewState(sections: sectionModels)
let deleteCommand = self?.tableView.rx.itemDeleted.asObservable()
.map(TableViewEditingCommand.DeleteItem)
let movedCommand = self?.tableView.rx.itemMoved
.map(TableViewEditingCommand.MoveItem)
if let deleteCommand = deleteCommand, let movedCommand = movedCommand, !sectionModels.isEmpty, let self = self {
Observable.of(deleteCommand, movedCommand)
.merge()
.scan(initialState) { (state: SectionedTableViewState, command: TableViewEditingCommand) -> SectionedTableViewState in
return state.execute(command: command)
}
.startWith(initialState)
.map {
$0.sections
}
.share(replay: 1)
.bind(to: self.tableView.rx.items(dataSource: self.dataSource!))
.disposed(by: self.disposeBag)
}
}).disposed(by: disposeBag)
tableView.rx.itemSelected
.asDriver()
.drive(onNext: onItemSelected)
.disposed(by: disposeBag)
view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.size.equalToSuperview()
}
tableView.separatorColor = .clear
if supportsDragging {
tableView.dragDelegate = self
tableView.rx.itemMoved
.asDriver()
.drive(onNext: onItemMoved)
.disposed(by: disposeBag)
}
tableView.rx.setDelegate(self).disposed(by: disposeBag)
}
}
enum TableViewEditingCommand {
case MoveItem(event: ItemMovedEvent)
case DeleteItem(IndexPath)
}
struct SectionedTableViewState<T: AnimatableSectionModelType> {
fileprivate var sections: [T]
init(sections: [T]) {
self.sections = sections
}
func execute(command: TableViewEditingCommand) -> SectionedTableViewState {
switch command {
case .DeleteItem(let indexPath):
var sections = self.sections
var items = sections[indexPath.section].items
items.remove(at: indexPath.row)
sections[indexPath.section] = T(original: sections[indexPath.section], items: items)
return SectionedTableViewState(sections: sections)
case .MoveItem(let moveEvent):
var sections = self.sections
var sourceItems = sections[moveEvent.sourceIndex.section].items
var destinationItems = sections[moveEvent.destinationIndex.section].items
if moveEvent.sourceIndex.section == moveEvent.destinationIndex.section {
destinationItems.insert(destinationItems.remove(at: moveEvent.sourceIndex.row),
at: moveEvent.destinationIndex.row)
let destinationSection = T(original: sections[moveEvent.destinationIndex.section], items: destinationItems)
sections[moveEvent.sourceIndex.section] = destinationSection
return SectionedTableViewState(sections: sections)
} else {
let item = sourceItems.remove(at: moveEvent.sourceIndex.row)
destinationItems.insert(item, at: moveEvent.destinationIndex.row)
let sourceSection = T(original: sections[moveEvent.sourceIndex.section], items: sourceItems)
let destinationSection = T(original: sections[moveEvent.destinationIndex.section], items: destinationItems)
sections[moveEvent.sourceIndex.section] = sourceSection
sections[moveEvent.destinationIndex.section] = destinationSection
return SectionedTableViewState(sections: sections)
}
}
}
}