1

The summary of my problem is; when the tableView.reloadData() is called upon firestore's local changes on the first time the view loads up it works and updates as it supposed to. However, after I switch forth and back with other viewControllers, although initially on viewDidAppear() tableView get's reloaded, upon local changes it no longer does so.

I've included a simpler version of my project to better explain and to make it reproducable;

Class Item

let db = Firestore.firestore()

static var list = [String:[String:Any]]()
static var listenerSet = Bool()
static var listener: ListenerRegistration!

func setListener(completion: @escaping (String) -> Void) {
    if !Item.listenerSet {
        print("Attaching item document listener.")
        Item.listener = db.collection("Items").document("default").addSnapshotListener({ (document, error) in
            let source = document!.metadata.hasPendingWrites ? "Local" : "Server"
            print("Updating item data from the \(source).")
            Item.listenerSet = true
            Item.list = document?.get("List") as! [String:[String:Any]]
            completion("Item data is set")
        })
    } else {
        completion("Item listener already exists")
    }
}

func add(itemID:String) {
    let itemRef = db.collection("Items").document("default")
    itemRef.setData([
        "List": [
            itemID : [
                "Count": FieldValue.increment(1.0),
            ]]
    ], merge:true)
}

FirstViewController:UIViewController

let item = Item()
var itemList = [[String:Any]]()

@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var switchBar: UISegmentedControl!

override func viewDidLoad() {
    super.viewDidLoad()
    // Setting the table views
    tableView.delegate = self
    tableView.dataSource = self
}

override func viewDidAppear(_ animated: Bool) {
    print("View Did appear") // prints OK
    item.setListener() { (result) in
        print(result)
        loadData()
    }
}

func loadData() {
    itemList.removeAll()
    var counter = 0
    for (id,data) in Item.list {
        itemList.append(data)
        itemList[counter]["ID"] = id
        counter += 1
    }
    tableView.reloadData()
}

@IBAction func switchBarChanged(_ sender: UISegmentedControl) {
    let storyBoard = UIStoryboard(name: "Main", bundle: nil)
    switch switchBar.selectedSegmentIndex {
    case 1:
        let vc = storyBoard.instantiateViewController(identifier: "secondViewController")
        show(vc, sender: self)
    default:
        break
    }
}

Extension FirstViewController: UITableViewDataSource,UITableViewDelegate

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    print("These are items:\(itemList)")
    print(itemList.count)
    print(tableView.bounds.height)
    return itemList.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "itemCell") as! ItemCell
    let itemID = itemList[indexPath.row]["ID"] as! String
    let itemName = itemList[indexPath.row]["Name"] as! String
    let itemCount = itemList[indexPath.row]["Count"] as! Double
    cell.itemID = itemID
    cell.itemName.text = itemName
    cell.itemCount.text = String(itemCount)
    print("Items within the tableView \(itemList)") // prints only in the first run whenever there is an upload, or when refreshed.
    return cell
}

func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {

    let add = UIContextualAction(style: .normal, title: "Add", handler: { (action, view, completionHandler) in
        item.add(itemID:(itemList[indexPath.row]["ID"] as! String))
        completionHandler(true)
    })

    add.image = UIImage(systemName: "plus")

    let configuration = UISwipeActionsConfiguration(actions: [add])
    configuration.performsFirstActionWithFullSwipe = true
    return configuration
}

Class ItemCell:UITableViewCell

var itemID = String()
@IBOutlet weak var itemName: UILabel!
@IBOutlet weak var itemCount: UILabel!

SecondViewController:UIViewController

@IBOutlet weak var switchBar: UISegmentedControl!

override func viewDidLoad() {
    super.viewDidLoad()
}

@IBAction func switchBarChanged(_ sender: UISegmentedControl) {
    let storyBoard = UIStoryboard(name: "Main", bundle: nil)
    switch switchBar.selectedSegmentIndex {
    case 0:
        let vc = storyBoard.instantiateViewController(identifier: "firstViewController")
        show(vc, sender: self)
    default:
        break
    }
}

Now this is the flow I'm having issue on;

  • Application loads, FirstViewController loads, viewDidAppear kicks in. tableView loads.
  • When swiped right, the value in firestore server increments by one, snapshot listener kicks in, tableview reloads and everything works as it supposed to be.
  • After I switch to SecondViewController and come back to FirstViewController, viewDidAppear kicks in tableView loads.
  • This time however, when I swipe right again, the value in firestore server still increments by one and the snapshot listener kicks in, updating the Item.list. static var ItemList gets updated, and tableView does reload, however this time, only numberOfRowsInSection method works as it prints the count and updated itemList, then nothing happens cellForRowAt doesn't work and rows don't get updated.
  • Now at this stage if I go SecondViewController and comeback to FirstViewController the rows are updated, but when swiped right still no avail.

What am I missing here ? Thanks all for your replies.

Melih Alan
  • 79
  • 1
  • 9
  • reload table view on mainthread – Shivam Gaur Apr 30 '20 at 12:04
  • Hi @ShivamGaur, thanks for the reply but I've already tried this; `DispatchQueue.main.async { tableView.reloadData() }` Still doesn't work, I've also tried embedding in VC's 'loadData()' method, again doesn't do anything. – Melih Alan Apr 30 '20 at 12:11

0 Answers0