I am having some issues with the RxDataSources cell reload animations for RxSwift. I have a simple table setup like so:
import UIKit
import RxDataSources
import RxCocoa
import RxSwift
import Fakery
class ViewController1: UIViewController {
#IBOutlet weak var tableView: UITableView!
let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
}
private func setupTableView() {
tableView.register(UINib(nibName: "TestTableViewCell", bundle: nil), forCellReuseIdentifier: "cell")
let dataSource = RxTableViewSectionedAnimatedDataSource<SectionOfTestData>(
animationConfiguration: AnimationConfiguration(insertAnimation: .none, reloadAnimation: .none, deleteAnimation: .none),
configureCell: { dataSource, tableView, indexPath, element in
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TestTableViewCell
cell.testData = element
return cell
})
someData
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: bag)
}
let someData = BehaviorRelay<[SectionOfTestData]>(value: [SectionOfTestData(items: [
TestData(color: .red, name: "Henry"),
TestData(color: .blue, name: "Josh")
])])
#IBAction func didTapUpdateButton(_ sender: Any) {
let colors: [UIColor] = [.blue, .purple, .orange, .red, .brown]
let items = someData.value.first!.items
// Add random data when button is tapped
someData.accept([SectionOfTestData(items: items + [TestData(color: colors.randomElement()!, name: Faker().name.firstName())])])
}
}
The models:
struct TestData {
let color: UIColor
let name: String
}
extension TestData: IdentifiableType, Equatable {
typealias Identity = Int
var identity: Identity {
return Int.random(in: 0..<20000)
}
}
struct SectionOfTestData {
var items: [Item]
var identity: Int {
return 0
}
}
extension SectionOfTestData: AnimatableSectionModelType {
typealias Identity = Int
typealias Item = TestData
// Implement default init
init(original: SectionOfTestData, items: [Item]) {
self = original
self.items = items
}
}
class TestTableViewCell: UITableViewCell {
#IBOutlet weak var colorView: UIView!
#IBOutlet weak var nameLabel: UILabel!
var testData: TestData! {
didSet {
colorView.backgroundColor = testData.color
nameLabel.text = testData.name
}
}
}
When the button is tapped the BehaviorRelay is updated and the table seems to refresh however the "animations" are always the same. In the supplied code I have actually set all animation types to .none yet it is still performing an animation. If I try to change the animation type to another type such as .bottom again the animation is the same. What am I doing wrong here?
Is this a reload animation or insert animation? I have no idea if the table reloads or inserts when the data is updated, I can't find any information in the documents. Any pointers on this would be greatly appreciated!
Your problem is:
var identity: Identity {
return Int.random(in: 0..<20000)
}
RxDataSources uses the identity value in order to compute the changeset.
You have implemented it in a way that essentially returns a new value each time (unless you get a collision) so from the framework point of view, you are always removing all the items and adding new items. You can check this by implementing
decideViewTransition: { _, _, changeset in
print(changeset)
return .animated
}
To complete the accepted answer :
make a uuid and pass it to identity:
let id = UUID().uuidString
var identity: String {
return id
}
then the animations work perfectly.
Related
So, I have tried looking ALL over the internet and StackOverflow to find an answer, but I'm not sure what to even look for, so the answer may already have been shared. So I'm sorry in advance, as I am a noob. However, I still need help. (please!) I've got an app I'm working on with a tableview full of parts, with a details part page that gives details of the part (Part name, part number, description, etc.)
I have a delete button at the end of the page, and when you click it, it asks you if you want to delete, are you sure? If the user says yes, then the part deletes, but the delete
only deleted the LAST item from the tableview, the most recently added. Which I know, is because I've called the following function:
func deletePart() {
if let partToDelete = getPartsArray().last {
try! realm.write {
realm.delete(partToDelete)
}
}
with 'getPartsArray().last'
I'm trying to see how I can get the CURRENT selected part in the tableview to be deleted. Right now, I could have the second part from the top selected, and if I click THAT part's delete button, it will always delete the last part from the tableview.
Here's the code for the getPartsArray function:
func getPartsArray() -> [PartInfo] {
return getAllParts().map { $0 }
}
I (noobishly) have already tried: with 'getPartsArray().current' and apparently that's not a thing lol.
I was also thinking, since I'm using REALM / Mongo DB, I could find the part by it's ID? and then delete it? but I'm not sure how to find the current select part's id either.
Any help would be much appreciated. Thank you!
EDIT: here is my TableView Code:
//
// ViewAllPartsViewController.swift
// PartKart
//
// Created by Kiarra Julien on 10/20/21.
//
import Foundation
import UIKit
class ViewAllPartsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, CurrencyFormatter {
private var brain = PartKartBrain()
private var parts = [PartInfo]()
#IBOutlet var tableView: UITableView!
#IBAction func returnHome() {
dismiss(animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
let nib = UINib(nibName: "DemoTableViewCell", bundle: nil)
tableView.register(nib, forCellReuseIdentifier: "DemoTableViewCell")
tableView.delegate = self
tableView.dataSource = self
parts = brain.getPartsArray()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
parts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "DemoTableViewCell", for: indexPath) as! DemoTableViewCell
cell.partNameLabel.text = parts[indexPath.row].partName
// Convert string value to double
cell.partCostLabel.text = formatCurrency(value: parts[indexPath.row].partCost)
// String(format: "$%.2f", parts[indexPath.row].partCost)
cell.purchaseDateLabel.text = parts[indexPath.row].purchaseDate
// cell.textLabel?.text = parts[indexPath.row].partName
// cell.textLabel?.numberOfLines = 0countTotalParts()
// cell.textLabel?.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.headline)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
performSegue(withIdentifier: "showPartDetails", sender: parts[indexPath.row])
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
if let viewcontroller = segue.destination as? PartDetailsViewController {
viewcontroller.part = sender as? PartInfo
}
}
}
and here's where I call delete part:
class PartDetailsViewController: UIViewController, CurrencyFormatter {
//Store Information Labels
#IBOutlet weak var storeNameLabel: UILabel!
#IBOutlet weak var storeNumLabel: UILabel!
#IBOutlet weak var storeAddrLabel: UILabel!
//Part Information Labels
#IBOutlet weak var partNameLabel: UILabel!
#IBOutlet weak var partNumLabel: UILabel!
#IBOutlet weak var partDescLabel: UILabel!
#IBOutlet weak var partCostLabel: UILabel!
#IBOutlet weak var partQtyLabel: UILabel!
#IBOutlet weak var purchaseDateLabel: UILabel!
#IBOutlet weak var hasWarrantySwitch: UISwitch!
#IBOutlet weak var warrantyLengthLabel: UILabel!
//Mechanic Information Labels
#IBOutlet weak var mechanicNameLabel: UILabel!
#IBOutlet weak var mechanicNumLabel: UILabel!
#IBOutlet weak var mechanicAddrLabel: UILabel!
#IBOutlet weak var laborCostLabel: UILabel!
#IBOutlet weak var serviceDateLabel: UILabel!
var part: PartInfo?
let brain = PartKartBrain()
#IBAction func deletePartBtn(_ sender: UIButton) {
// Declare Alert message
let dialogMessage = UIAlertController(title: "Confirm", message: "Are you sure you want to delete this part?", preferredStyle: .alert)
// Create OK button with action handler
let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
print("Ok button tapped")
// I CALL DELETE PART RIGHT HEREEE!
self.brain.deletePart()
// delay and then dismiss the page
let delayInSeconds = 0.5
DispatchQueue.main.asyncAfter(deadline: .now() + delayInSeconds) { [unowned self] in
dismiss(animated: true, completion: nil)
}
})
// Create Cancel button with action handlder
let cancel = UIAlertAction(title: "Cancel", style: .cancel) { (action) -> Void in
print("Cancel button tapped")
}
//Add OK and Cancel button to dialog message
dialogMessage.addAction(ok)
dialogMessage.addAction(cancel)
// Present dialog message to user
self.present(dialogMessage, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
title = part?.partName
//Set the Store Info Labels Equal to actual data
storeNameLabel.text = part?.partName
storeNumLabel.text = part?.storeNumber
storeAddrLabel.text = part?.storeAddress // < ---- The address is cut off the screen!
//Set the Part Info Labels Equal to actual data
partNameLabel.text = part?.partName
partNumLabel.text = part?.partNumber
partDescLabel.text = part?.partDescription
if let partCost = part?.partCost {
partCostLabel.text = formatCurrency(value: partCost)
}
if let partQty = part?.partQuantity {
partQtyLabel.text = String(partQty)
}
purchaseDateLabel.text = part?.purchaseDate
//If there's no warranty, display 'N/A' instead
if part?.hasWarranty == true {
hasWarrantySwitch.isOn = true
warrantyLengthLabel.text = part?.warrantyLength
} else {
hasWarrantySwitch.isOn = false
warrantyLengthLabel.text = "N/A"
}
//Set the Mechanic Info Labels Equal to actual data
mechanicNameLabel.text = part?.mechanicName
mechanicNumLabel.text = part?.mechanicNumber
mechanicAddrLabel.text = part?.mechanicAddress
//laborCostLabel.text = part?.laborCost
if let laborCost = part?.laborCost {
laborCostLabel.text = formatCurrency(value: laborCost)
}
serviceDateLabel.text = part?.serviceDate
}
}
Let me state the question back to you
"How to I delete a selected row from my tableview"?
If that's the question let be provide a boiled down answer
First, don't do this when working with Realm
return getAllParts().map { $0 }
Realm objects are lazily loaded into a Results object. As soon as you run them against a high level function like map, reduce etc (storing them in an Array), they ALL get loaded into memory and if you have a large dataset, that will overwhelm the device. Additionally if you have sorting etc, your objects will get out of sync with the underlying Realm data.
Your best bet is to leverage Realm Results as your tableView dataSource - it behaves much like an array.
So here's a viewController class that has a parts Results object as a tableView datasource
class InStockVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
var partsNotificationToken: NotificationToken!
var partsResults: Results<PartsClass>! //the tableView dataSource
Assume the user taps or selects row #2 and then taps or clicks 'Delete'
//pseudocode
let selectedRow = tableView.selectedRow
let part = partResults[selectedRow]
try! realm.write {
realm.delete(part)
}
The above code determines which row in the table was selected, gets the part object from Results and then tells Realm to delete it.
As soon as that object is deleted from Realm, the partsResults object reflects that change and the object is automagically removed! The only thing you need to do is to reflect that change in the UI.
There are many ways of handling that with animations and so forth but to keep it simple, lets just reload the tableView so the deleted row is no longer displayed.
Note in the above code, there's also a var partsNotificationToken: NotificationToken!, that token is an observer of the results object - when something in the underlying data changes, the results object changes as well and the notification token fires an observer action to handle that change. Here's an example observer
self.partsNotificationToken = self.partsResults.observe { changes in
switch changes {
case .initial:
self.tableView.reloadData() //update the tableview after data is initially loaded
case .update(_, _, _, _):
self.tableView.reloadData() //update the tableView after changes
I am struggling to get my head around Firebase Real-time Database and to have it playing nicely with Table View in my restaurant ordering app!
Before reading my code (please don't laugh), what I am hoping to get my TableView is able to do are
1) sort the requests by category (using value retrieved from the key "RequestItemCategory" which has Int value of 1, 2, 3)
2) add the new request to the tableview AND sort the requests again according to the category
3) remove completed food request from the tableview when the customer or employee tick "complete order" on another device which will trigger a change to the "RequestCompleted" key of the requested child on Firebase to "true")
The only things that I can achieve after hours of mucking around are
when app starts, the app loads the table and displays all the requests in the right order according to their category but even the requests that are tagged completed on the Firebase are still displayed.
new orders are added to the bottom of the list but not sort into a category.
What I tried
1) Tag .queryEqual(toValue: "false", childKey: "RequestCompleted") to try to filter out completed requests returns an empty table (not nil information)
2) I can use a button to clear the requestArray then calls retrieveRequest() again. This will refresh the table and all requests will be in the right order but I like to have it done automatically if possible. Also, this method will create another observer so I ended multiple identical orders added to the list...
3) using .value instead of .childAdded - returns nil and the app crashes - I think I need to use for loops but not sure how to do it.
Please help thanks!
Codes are:
Class object
class Request {
var name : String = ""
var requestItem : String = ""
var seat : String = ""
var requestCategory : String = ""
}
Cell.swift
import UIKit
class CustomCellTableViewCell: UITableViewCell {
#IBOutlet var requestText : UITextField!
#IBOutlet var nameText: UITextField!
#IBOutlet var seatNumberText: UITextField!
#IBOutlet var timerText: UITextField!
#IBOutlet var cellBackground : UIView!
#IBOutlet var cellCategoryView : UIView!
#IBOutlet var cellCategoryLabel : UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
ViewController.swift
import UIKit
import Firebase
import SVProgressHUD
class MainPageViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate {
#IBOutlet weak var requestTableView: UITableView!
var requestArray : [Request] = [Request]()
override func viewDidLoad() {
super.viewDidLoad()
requestTableView.delegate = self
requestTableView.dataSource = self
requestTableView.register(UINib(nibName: "CustomCellTableViewCell", bundle: nil), forCellReuseIdentifier: "customRequestCell")
configureTableView()
retrieveRequest()
// Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customRequestCell", for: indexPath) as! CustomCellTableViewCell
cell.nameText.text = requestArray[indexPath.row].name
cell.seatNumberText.text = requestArray[indexPath.row].seat
cell.requestText.text = requestArray[indexPath.row].requestItem
cell.cellCategoryLabel.text = requestArray [indexPath.row].requestCategory
if cell.cellCategoryLabel.text == "1" {
cell.cellBackground.backgroundColor = UIColor.init(red: 142/255, green: 0/255, blue: 0/255, alpha: 0.65)
} else if cell.cellCategoryLabel.text == "2" {
cell.cellBackground.backgroundColor = UIColor.init(red: 234/255, green: 179/255, blue: 0/255, alpha: 0.75)
} else {
cell.cellBackground.backgroundColor = UIColor.init(red: 0/255, green: 113/255, blue: 119/255, alpha: 0.65)
}
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return requestArray.count
}
func configureTableView() {
requestTableView.rowHeight = UITableView.automaticDimension
requestTableView.estimatedRowHeight = 73
}
func retrieveRequest() {
let ref = Database.database().reference().child(“Restaurant”).child("Requests").queryOrdered(byChild: "RequestItemCategory")**.queryEqual(toValue: "false", childKey: "RequestCompleted")** adding .queryEqual doesn't work...
ref.observe(.childAdded) { (snapshot) in
let snapshotValue = snapshot.value as? [String : AnyObject] ?? [:]
let customerName = snapshotValue["Lastname"]
let customerSeatnumber = snapshotValue[“Seat”]
let customerRequestedItem = snapshotValue["RequestItem"]
let customerRequestedItemCategory = snapshotValue ["RequestItemCategory"]
let request = Request()
request.name = customerName as! String
request.seat = customerSeatnumber as! String
request.requestItem = customerRequestedItem as! String
request.requestCategory = "\(customerRequestedItemCategory!)"
self.requestArray.append(request)
self.configureTableView()
self.requestTableView.reloadData()
}
}
}
ps. the requestLabel.text is used to change the color of a particular UIView according to the category (RequestItemCategory)
I have the following issue. I use RxSwift to bind my datasource to an UITableView like this:
self.presenter.posts
.bind(to: self.tableView.rx.items(cellIdentifier: "ListPostsCell")) { index, model, cell in
if let cell = cell as? ImagePostTableViewCell {
cell.viewModel = model
cell.actionButton.rx.action = self.action(for: model.postId, index: index)
}
}.disposed(by: self.bag)
When the tableview reaches the end of the datasource count I request more data and update the datasource (infinite scroll) like this:
self.tableView.rx.willDisplayCell
.map { $0.indexPath.item }
.distinctUntilChanged()
.withLatestFrom(self.presenter.posts) { (item, posts) -> Bool in
return item == posts.count - 3
}
.filter { $0 }
.withLatestFrom(self.presenter.posts)
.map { UsersPosts.Request(after: $0.last?.postId) }
.bind(to: self.usersPostsInteractor.receiveUsersPosts.inputs)
.disposed(by: self.bag)
This works fine and I can scroll infinite as expected. The problem is within my Cell:
class ImagePostTableViewCell: UITableViewCell {
#IBOutlet weak var postImageView: UIImageView!
#IBOutlet weak var haiScoreLabel: UILabel!
#IBOutlet weak var lastVoteLabel: UILabel!
#IBOutlet weak var actionButton: UIButton!
#IBOutlet weak var activtiyIndicator: UIActivityIndicatorView!
var bag: DisposeBag?
var viewModel: ImagePostTableViewCellViewModel? {
didSet {
let bag = DisposeBag()
guard let vm = viewModel else { return }
vm.image.asDriver(onErrorJustReturn: UIImage())
.drive(onNext: { image in
self.updateAppearanceFor(image)
}).disposed(by: bag)
self.haiScoreLabel.text = vm.haiScoreText
self.lastVoteLabel.text = vm.lastVoteText
self.actionButton.setTitle(vm.actionLabelText, for: .normal)
self.activtiyIndicator.startAnimating()
self.bag = bag
}
}
func updateAppearanceFor(_ image: UIImage?, animated: Bool = true) {
DispatchQueue.main.async {
if animated {
UIView.animate(withDuration: 0.5) {
self.displayImage(image)
}
} else {
self.displayImage(image)
}
}
}
private func displayImage(_ image: UIImage?) {
if let image = image {
self.show(image: image)
} else {
self.hide()
}
}
private func show(image: UIImage) {
self.postImageView.image = image
self.postImageView.alpha = 1.0
self.haiScoreLabel.alpha = 1.0
self.lastVoteLabel.alpha = 1.0
self.activtiyIndicator.stopAnimating()
}
private func hide() {
self.postImageView.image = nil
self.postImageView.alpha = 0
self.haiScoreLabel.alpha = 0
self.lastVoteLabel.alpha = 0
self.activtiyIndicator.startAnimating()
}
override public func prepareForReuse() {
super.prepareForReuse()
self.displayImage(.none)
self.viewModel = nil
self.bag = nil
}
}
As you can see there is an animation which ends up in an super smooth scrolling behaviour - but because of the datasource update, with every reload the animation starts and unchanged content flashes because of the animation.
In the end I want to avoid the reload for the visible cell but I really desperate here.
Any hints to follow? Thanks in advance!!!
I am able to bind an Observable sequence of data to a table.
Now lets say i have a button on each cell which on click changes the
label of that cell to new value. How to do this ?
I have done so far as follows
I have created an #IBAction for that button in the cell pointing class
then i am doing
label.text = "new text"
but when i scroll down then scroll up, the label show previous value not the new value
previously when i use array and set each value to a cell i used to update that array item and called tableview.reloadData.
how can i do this in RxSwift??
I have done so far
tableview.dataSource = nil (then)
myData.bindTo ... (bind again)
This does not seem to me the right way. so what is the appropriate way to deal with this??
I am able to achive this with RxSwiftCommunity Action
https://github.com/RxSwiftCommunity/Action
ViewController
variable.asObservable().bindTo(mytable.rx.items(cellIdentifier: "cell")) { (row, person, cell) in
if let cellToUse = cell as? TableViewCell {
cellToUse.person = Variable(person)
cellToUse.button1.rx.action = cellToUse.action
}
}.addDisposableTo(disposeBag)
and in cell
class TableViewCell: UITableViewCell {
#IBOutlet weak var label1: UILabel!
#IBOutlet weak var label2: UILabel!
#IBOutlet weak var button1: UIButton!
let disposeBag = DisposeBag()
let action = CocoaAction { input in
return .just(input)
}
var person : Variable<Person>!{
didSet {
self.updateUI()
}
}
private func updateUI(){
person.asObservable()
.subscribe(onNext: {
person in
let name : Observable<String> = Observable.of(person.name)
let age : Observable<String> = Observable.of("\(person.age)")
_ = name.bindTo(self.label1.rx.text)
_ = age.bindTo(self.label2.rx.text)
}).addDisposableTo(disposeBag)
action.elements
.asObservable()
.subscribe(onNext: {
_ in
self.label2.text = "asd"
}).addDisposableTo(disposeBag)
}
}
like before not sure if this is the right way but it worked for me
thanks to (https://rxswift.slack.com/messages/#fpillet) for showing me the way
I have one UITableView populated by reactive viewmodel using RxSwift, pagination and refresh are working well. The viewModel.dataSource() is consuming my API and sometime I can receive a empty result parsed as error type.
I want to catch this error and create an empty state, hiding tableview and showing a emptyViewState. I thought I could make it with the catchError.
My problem is after catchError, the dataSource is disposed and I couldn't be able to recovery the empty state and repopulated the tableview, I tried to recreate the dataSource calling self.bindDataSource() but I getting fatal error.
There is a way to avoid dataSource disposed ? How can I reconnect / rebuild the dataSource to recovery from the empty state ?
class MyViewControl: UIViewController {
fileprivate let disposeBag = DisposeBag()
fileprivate let viewModel = ViewModel()
let dataSource = SearchViewModel.SearchDataSource()
#IBOutlet fileprivate weak var tableView: UITableView!
#IBOutlet weak var emptyStateView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// When I disable tableview, can see a hidden view with empty state message and one button
viewModel.isTableViewHidden
.bindTo(tableView.rx.isHidden)
.addDisposableTo(disposeBag)
self.setupTableView()
}
fun setupTableView() {
// ... setup table view
self.bindDataSource()
}
fileprivate func bindDataSource() {
// Bind dataSource from search to UITableView
viewModel.dataSource()
.debug("[DEBUG] Loading Search Tableview ")
.bindTo( tableView.rx.items(dataSource: dataSource) )
.addDisposableTo( disposeBag )
}
#IBAction fileprivate func emptyStateAction(_ sender: UIButton) {
// Do something and try to recreate the bindDataSource
self.bindDataSource()
}
}
class SearchViewModel {
private let disposeBag = DisposeBag()
typealias SearchDataSource = RxTableViewSectionedReloadDataSource<PaginationStatus<WorkerEntity>>
let isTableViewHidden = BehaviorSubject<Bool>(value: false)
// Controls to refresh and paging tableview
let refreshTrigger = BehaviorSubject<Void>(value:())
let nextPageTrigger = PublishSubject<Void>()
// Others things happing herer
func dataSource() -> Observable<[PaginationStatus<WorkerEntity>]> {
return self.refreshTrigger.debug("[DEBUG] Refreshing dataSource")
.flatMapLatest { [unowned self] _ -> Observable<[PaginationStatus<WorkerEntity>]> in
// Access the API and return dataSource
}
.catchError { [unowned self] error -> Observable<[PaginationStatus<WorkerEntity>]> in
// Hidden the tableview
self.isTableViewHidden.onNext(true)
// Do others things
return Observable.of([PaginationStatus.sectionEmpty])
}
}
}
when you bindDataSource() you dont reinitialised your datasource, so you bind it to a error event.
You need to init it, to bind it again. And you might want to remove your binding too
let disposeBagTableView = DisposeBag()
//remove
let dataSource = SearchViewModel.SearchDataSource()
fileprivate func bindDataSource() {
// Bind dataSource from search to UITableView
disposeBagTableView = DisposeBag()
SearchViewModel.SearchDataSource()
.debug("[DEBUG] Loading Search Tableview ")
.bindTo( tableView.rx.items(dataSource: dataSource) )
.addDisposableTo( disposeBagTableView )
}