0

i have a viewcontroller with a tableview, and when user clicks on the cell, it goes to VC2. When the user has performed a action (and updated the values in VC2), i use self.dismiss(animated: true, completion: nil) to go back to the viewcontroller with the tableview, however the tableview (once the user has gone back to the tableview) is showing duplicated rows, but the child is succesfully deleted in firebase, and a new child is created - however the tableview is showing the childs that are not deleted twice.

This is all the relevant code in VC1:

class PostMessageListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet weak var table: UITableView!

    var topicID:namePosts?
    let currentUserID = Auth.auth().currentUser?.uid
    var posts = [Post]()

    lazy var refresher: UIRefreshControl = {

        let refreshControl = UIRefreshControl()
        refreshControl.tintColor = .white
        refreshControl.addTarget(self, action: #selector(requestData), for: .valueChanged)

        return refreshControl
    }()
    @objc
    func requestData() {
        self.table.reloadData()
        refresher.endRefreshing()
    }

    func reloadData(){

        table.reloadData()
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        self.table.separatorStyle = UITableViewCellSeparatorStyle.none

        table.refreshControl = refresher

        //DataManager.shared.firstVC = self

        self.table.delegate = self
        self.table.dataSource = self
        let postCell = UINib(nibName: "PostTableViewCell", bundle: nil)
        self.table.register(postCell, forCellReuseIdentifier: "cell")

        self.posts.removeAll()
                   Database.database().reference().child("posts").child(postID!.name)
            .observe(.childAdded) { (snap) in

                if snap.exists() {

                    //declare some values here...

                        self.posts.append( //some values here)
                        self.posts.sort(by: {$0.createdAt > $1.createdAt})
                        self.table.reloadData()

                    })
                }
                else {
                    self.table.reloadData()
                }

        }
        //observe if a post is deleted by user
        Database.database().reference().child("posts").child("posts").observe(.childRemoved) { (snapshot) in

            let postToDelete = self.indexOfPosts(snapshot: snapshot)
            self.posts.remove(at: postToDelete)
            self.table.reloadData()
            //self.table.deleteRows(at: [NSIndexPath(row: questionToDelete, section: 1) as IndexPath], with: UITableViewRowAnimation.automatic)

            //self.posts.remove(at: indexPath.row)
        }

    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.posts.count

    }

    func indexOfPosts(snapshot: DataSnapshot) -> Int {
        var index = 0
        for  post in self.posts {
            if (snapshot.key == post.postID) {
                return index
            }
            index += 1
        }
        return -1
    }

EDIT: Forgot to say, but i have used this code in another Viewcontroller, and it works fine there. However i just copied my code from that to this one, and deleted a bunch of stuff i didn't need, however i cant seem to find what i am missing in this one.

Matt Gilbert
  • 113
  • 1
  • 13
  • The simple way to handle that is to attach an observer in VC1 to the node your tableView is populated from. When that node is updated, the observer will receive that event (add, change, remove) and then you can perform the same action on your dataSource array, then refresh your tableView. *or* when any action occurs, reset the dataSource array e.g. myDatasourceArray = [], reload the data and update the tableview. – Jay Feb 21 '19 at 16:43
  • Yes but i already have this in place in VC1, the `.childRemoved` observer will do the index update, aswell as reload the tableview.. – Matt Gilbert Feb 21 '19 at 16:46
  • It's not clear why there are two arrays needed for your datasource. You've got one, `self.posts` which would be the dataSource for the tableView but then there's another `self.indexOfPosts` which is not cleary defined in your code (why do you need indexing in that var?). Generally speaking it's cleaner to have just the dataSource array and add, change and remove from that instead of two separate arrays. I would guess that's why your data is getting out of sync. – Jay Feb 21 '19 at 16:50
  • I also note that your .childAdded and .childRemoved are observing two different nodes. Not sure why that is. – Jay Feb 21 '19 at 17:07
  • Woops, wrong copy, but what do you suggest i do with only having one datasource array and remove it with this way and updating tableview? – Matt Gilbert Feb 21 '19 at 18:40

1 Answers1

0

This may not be the answer but it may lead to an answer. As noted in the comments there are two arrays being used to manage the dataSource for the tableView. One contains the data and one is using an indexing technique - I believe that may lead to issues, like the one described in the question.

The other issue is that when every child is intially added, we re-sort the array and then refresh the tableView - that can lead to delays and flicker. (flicker = bad)

So let establish a couple of things. First a class that holds the posts

PostClass {
   var post_id = ""
   var post_text = ""
   var creation_date = ""
}

second the Firebase structure, which is similar

posts
   post_id_0
      text: "the first post"
      timestamp: "20190220"
   post_id_1
      text: "the second post"
      timestamp: "20190221"

then a little trick to populate the datasource and leave a child added observer. This is important as you don't want to keep refreshing the tableView with every child it as may (will) flicker. So we leverage that childAdded events always come before .value events so the array will populate, and then .value will refresh it once, and then we will update the tableView each time after. Here's some code - there's a lot going on so step through it.

var postsArray = [String]()
var initialLoad = true

func ReadPosts() {
    let postsRef = self.ref.child("posts").queryOrdered(byChild: "timestamp")
    postsRef.observe(.childAdded, with: { snapshot in
        let aPost = PostClass()
        aPost.post_id = snapshot.key
        aPost.post_text = snapshot.childSnapshot("text").value as! String
        aPost.creation_date = snapshot.childSnapshot("timestamp").value as! String
        self.postsArray.append(aPost)

        //upon first load, don't reload the tableView until all children are loaded
        if ( self.initialLoad == false ) { 
            self.postsTableView.reloadData()
        }
    })

    //when a child is removed, the event will contain that child snapshot
    //  we locate the child node via it's key within the array and remove it
    //  then reload the tableView
    postsRef.observe(.childRemoved, with: { snapshot in
        let keyToRemove = snapshot.key
        let i = self.postsArray.index(where: { $0.post_id == keyToRemove})
        self.postsArray.remove(at: i)
        self.postsTableView.reloadData()
    })

    //this event will fire *after* all of the child nodes were loaded 
    //  in the .childAdded observer. So children are sorted, added and then
    //  the tableView is refreshed. Set initialLoad to false so the next childAdded
   //   after the initial load will refresh accordingly.
    postsRef.observeSingleEvent(of: .value, with: { snapshot in
        self.postsTableView.reloadData()
        self.initialLoad = false
    })
}

Things to note

We are letting Firebase doing the heavy lifting and ordering the nodes by creation_date so they come in order.

This would be called from say, viewDidLoad, where we would set the initialLoad class var to true initially

Jay
  • 34,438
  • 18
  • 52
  • 81