UITableView reload not working

Hello,

I'm working on this messaging app where users have the option of blocking other users. The problem I'm having is reloading the UITableView after a user has been blocked. The first block of code is to block the users:

MemberVC

func blockUserOption(forMember memberData: String, userHasBeenBlocked: Bool, completion: ((Error?) -> Void)?) {
    BLOCKED_USERS_COLLECTION.document(currentUID!).getDocument { (snapshot, error) in
      let data = [self.memberDocumentID: userHasBeenBlocked]
      if snapshot?.exists == true {
        BLOCKED_USERS_COLLECTION.document(self.currentUID!).updateData(data)
      } else {
        BLOCKED_USERS_COLLECTION.document(self.currentUID!).setData(data)
      }}} 

//Here is how I'm executing the code above:

       let blockUser = UIAlertAction(title: "Block", style: .default) { (action) in
        let alert = UIAlertController(title: "Block", message: "Are you sure you want to block" + " " + "\(self.memberName as Any)?", preferredStyle: .alert)
        alert.view.tintColor = .brick
         
        alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { (action: UIAlertAction) in
          self.blockUserOption(forMember: self.memberDocumentID, userHasBeenBlocked: true, completion: nil)
           self.userDefaults.setValue("yes", forKey: RELOAD)
        }))
        alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { (action: UIAlertAction!) in
        }))
        self.present(alert, animated: true, completion: nil)
      }


Once the user has been blocked, the current view controller gets dismissed and the user goes back to the view controller where the table view needs to reload and remove the blocked user from the list.

MemberListVC

   override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(true)
    let reloadQuery = userDefaults.string(forKey: RELOAD)
     
    if reloadQuery == "yes" {
       
      self.match.removeAll()
       
      let query = MATCH_INFO_COLLECTION.document(currentUID!)
        .collection("info").order(by: TIMESTAMP, descending: false)
       
      query.limit(to: 10).getDocuments { (snapshot, error) in
              self.filterUsersThatWereBlocked { blockedUsers in
                guard let last = snapshot?.documents.last else { return }
                guard let snap = snapshot else { return }
                snap.documents.forEach({ document in
                   
                  let data = document.data()
                  let memberId = data[DOCUMENT_ID] as? String ?? ""
                  let memberAge = data[AGE] as? Int ?? 0
                  let memberName = data[FIRST_NAME] as? String ?? ""
                  let memberImageUrl = data[PROFILE_IMAGE_URL] as? String ?? ""
                  let memberCurrentCity = data[CURRENT_CITY] as? String ?? ""
                  let timestamp = data[TIMESTAMP] as? Double ?? 0.0
                   
                  let matches = Match(memberId: memberId, memberAge: memberAge ,memberName: memberName, memberImageUrl: memberImageUrl, memberCurrentCity: memberCurrentCity, matchTimestamp: Date(timeIntervalSince1970: timestamp))
                   
                  guard matches.memberId != Auth.auth().currentUser?.uid else { return }
                  guard blockedUsers[matches.memberId] == nil else { return }
                   
                  self.match.append(matches)
                   
                  self.matchedMessagesTV.reloadData()
                })
                self.lastDocument = last
              }
      }
      userDefaults.setValue("no", forKey: RELOAD)
    } else {
      print("NOTHING TO DO HERE")
    }
  }


   func filterUsersThatWereBlocked(completion: @escaping([String: Bool]) -> Void) {
    guard let currentUid = Auth.auth().currentUser?.uid else { return }
    BLOCKED_USERS_COLLECTION.document(currentUid).getDocument { (snapshot, error) in
      guard let data = snapshot?.data() as? [String: Bool] else {
        completion([String: Bool]())
        return
      }
      completion(data)
    }}


Now, if I close and re-open the app, the users that were blocked, do not show up. I'm not sure if the issue is because I'm trying to reload the view inside "viewDidAppear", I tried with "viewWillAppear" and the outcome is the same. Any help is greatly appreciated. Thank you!

  • Could you show the tableView delegate code, notably tableView(cellForRowAt) ?

Add a Comment

Accepted Reply

User is blocked in alert action

So that's very likely an async issue.

How do you transition to MemberListVC ? By dismissing ? Where is it done ?

What you should do is put this transition trigger in the handler of the Yes button

        alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { (action: UIAlertAction) in

To be sure the transition is only triggered after user was blocked.

Replies

Per your request:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let tvCell = matchedMessagesTV.dequeueReusableCell(withIdentifier: "MatchedMessagesCell", for: indexPath) as! MatchedMessagesCell
    tvCell.configureMatchedMessagesCell(match: match[indexPath.row])
     
    tvCell.selectionStyle = .none
    tvCell.backgroundColor = .beige
     
    tvCell.profileImageBlur.isHidden = true
    tvCell.profileNameBlur.isHidden = true
    tvCell.profileCityBlur.isHidden = true
    tvCell.timeStampBlur.isHidden = true
    tvCell.matchAvailableLbl.isHidden = true
     
    if indexPath.row >= 2 {
      tvCell.profileImageBlur.isHidden = false
      tvCell.profileNameBlur.isHidden = false
      tvCell.profileCityBlur.isHidden = false
      tvCell.timeStampBlur.isHidden = false
      tvCell.matchAvailableLbl.isHidden = false
    }
    return tvCell
  }

and here is the cell class:

class MatchedMessagesCell: UITableViewCell {
   
  @IBOutlet weak var profileImage: UIImageView!
  @IBOutlet weak var profileNameLbl: UILabel!
  @IBOutlet weak var profileCityLbl: UILabel!
  @IBOutlet weak var timeStampLbl: UILabel!
  @IBOutlet weak var profileImageBlur: UIVisualEffectView!
  @IBOutlet weak var profileCityBlur: UIVisualEffectView!
  @IBOutlet weak var profileNameBlur: UIVisualEffectView!
  @IBOutlet weak var timeStampBlur: UIVisualEffectView!
   
  func configureMatchedMessagesCell(match: Match) {
     
    profileImage.loadImage(with: match.memberImageUrl)
    profileNameLbl.text = match.memberName + "(" + "\(match.memberAge)" + ")"
    profileCityLbl.text = match.memberCurrentCity
    timeStampLbl.text = match.timestamp.timeAgoToDisplay()

  }
}


There are other points to check:

  • how is match array loaded ?
  • what is number of cells, as defined in
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  • just added that and the model too.

  • Everything works as expected with the exception of reloading the table view once I go back to the memberListVCr. The table view needs to reload as soon as the other views are removed from the stack.

Add a Comment
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    
    return match.count
  }
class Match {
   
  let memberId: String
  let memberAge: Int
  let memberName: String
  let memberImageUrl: String
  let memberCurrentCity: String
  let timestamp: Date!
   
  init(memberId: String, memberAge: Int, memberName: String, memberImageUrl: String, memberCurrentCity: String, timestamp: Date) {
     
    self.memberId = memberId
    self.memberAge = memberAge
    self.memberName = memberName
    self.memberImageUrl = memberImageUrl
    self.memberCurrentCity = memberCurrentCity
    self.timestamp = timestamp
  }
}

and these are the properties in the main VC:

  var match = [Match]()
  var lastDocument: DocumentSnapshot? = nil
  let userDefaults = UserDefaults.standard
  var match = [Match]()
  var lastDocument: DocumentSnapshot? = nil
  let userDefaults = UserDefaults.standard

Thanks. But that does not show how match array is loaded or updated and where.

To load the array inside ViewDidLoad in calling this function "fetchUsers()". Inside this block lastDocument is included.

And userDefaults is loaded in the MemberVC right after the user block is called.

func fetchUsers() {
    if lastDocument == nil {
      let query = MATCH_INFO_COLLECTION.document(currentUID!)
        .collection("info").order(by: MATCH_TIMESTAMP, descending: false)
       
      query.limit(to: 10).getDocuments { (snapshot, error) in
        
              self.filterUsersThatWereBlocked { blockedUsers in
                 
                guard let last = snapshot?.documents.last else { return }
                guard let snap = snapshot else { return }
                snap.documents.forEach({ document in
                   
                  let data = document.data()
                  let memberId = data[DOCUMENT_ID] as? String ?? ""
                  let memberAge = data[AGE] as? Int ?? 0
                  let memberName = data[FIRST_NAME] as? String ?? ""
                  let memberImageUrl = data[PROFILE_IMAGE_URL] as? String ?? ""
                  let memberCurrentCity = data[CURRENT_CITY] as? String ?? ""
                  let timestamp = data[TIMESTAMP] as? Double ?? 0.0
                   
                  let matches = Match(memberId: memberId, memberAge: memberAge ,memberName: memberName, memberImageUrl: memberImageUrl, memberCurrentCity: memberCurrentCity, timestamp: Date(timeIntervalSince1970: timestamp))
                   
                  guard matches.memberId != Auth.auth().currentUser?.uid else { return }
                   
                  guard blockedUsers[matches.memberId] == nil else { return }
                   
                  self.match.append(matches)
                   
                   
                  self.matchedMessagesTV.reloadData()
                })
                self.lastDocument = last
              }}
    } else {
      matchedMessagesTV.tableFooterView = createSpinnerFooter()
      let query = MATCH_INFO_COLLECTION.document(currentUID!)
        .collection("info").order(by: MATCH_TIMESTAMP, descending: false)
       
      query.start(afterDocument: lastDocument!)
        .limit(to: 10).getDocuments { (snapshot, error) in
       
                self.filterUsersThatWereBlocked { blockedUsers in
                  DispatchQueue.main.async {
                    self.matchedMessagesTV.tableFooterView = nil
                  }
                  guard let last = snapshot?.documents.last else { return }
                  guard let snap = snapshot else { return }
                   
                  snap.documents.forEach({ document in
                     
                    let data = document.data()
                    let memberId = data[DOCUMENT_ID] as? String ?? ""
                    let memberAge = data[AGE] as? Int ?? 0
                    let memberName = data[FIRST_NAME] as? String ?? ""
                    let memberImageUrl = data[PROFILE_IMAGE_URL] as? String ?? ""
                    let memberCurrentCity = data[CURRENT_CITY] as? String ?? ""
                    let timestamp = data[TIMESTAMP] as? Double ?? 0.0
                     
                    let matches = Match(memberId: memberId, memberAge: memberAge ,memberName: memberName, memberImageUrl: memberImageUrl, memberCurrentCity: memberCurrentCity, timestamp: Date(timeIntervalSince1970: timestamp))
                     
                    guard matches.memberId != Auth.auth().currentUser?.uid else { return }
                     
                    guard blockedUsers[matches.memberId] == nil else { return }
                     
                    self.match.append(matches)
                     
               
                    self.matchedMessagesTV.reloadData()
                  })
                  self.lastDocument = last
                }}}
  }

What is the goal of following statement ? In which cases does Users[matches.memberId] == nil and != nil ?

                  guard blockedUsers[matches.memberId] == nil else { return }

I'm not sure to understand because logic is a bit complex.

If blockedUsers[matches.memberId] is nil then you append matches ? Is it what you want ?

The goal of that statement is to remove the user that is logged in when I'm filtering the array. As mentioned, that section works properly, the issue I'm having is reloading/refreshing the table view when i go back to MemberListVC. Is viewDidAppear or viewWillAppear OK to use to reload/refresh the table view?

Sorry, after all those posts I still don't get a clear understanding of the problem.

Could you confirm that this is the use case and answer all questions:

  • You block a user

where is it done precisely in code ? Is it in the alert action ?

  • after this app returns to MemberListVC
  • when there, table does not reload properly

-> what do you expect exactly to be displayed in tableView ?

-> What do you get ?

Problem could be due to the fact that when you go to MemberListVC, the completion code where you block user has not completed (async call). The tableView datasource is then not updated, hence blocked user not taken into account.

User is blocked in alert action, once done that MemberVC is dismissed and app goes to MemberListVC. Let's say in MemberListVC you have 3 users, you open one of the profiles with MemberVC and then you block the user. When the app goes back to the MemberListVC, there should be 2 users only.

I'm going to do a test delaying the dismissal to MemberListVC, let's see if that gives time for the async call to finish and the table view reloads properly.

  • You can delay or you can add print statement at each step and check in which order they appear.

Add a Comment

User is blocked in alert action

So that's very likely an async issue.

How do you transition to MemberListVC ? By dismissing ? Where is it done ?

What you should do is put this transition trigger in the handler of the Yes button

        alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { (action: UIAlertAction) in

To be sure the transition is only triggered after user was blocked.

You were correct, after delaying the dismissal of the view controllers by 2 seconds, the table view is refreshed with no issues. To transition back to the MemberListVC I was using "unWind" but the problem there is that as soon as you press the button, it jumps back to the initial VC and there was no time for the async call to finish. So what I end up doing is adding the 2 seconds delay (it shows a loader while is running) and calling the returnToInitialVC() function, this function removes the 2 views from the stack. Thank you for your time!

DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
        self.returnToInitialVC()
        self.showLoader(false)
      }

func returnToInitialVC() {
    let viewControllers: [UIViewController] = self.navigationController!.viewControllers as [UIViewController]
    self.navigationController!.popToViewController(viewControllers[viewControllers.count - 3], animated: true)
  }

Thanks for feedback.

Delaying is just a patch. You cannot guarantee that in some circumstances you would not need 2.5 s. Why don't you call returnToInitialVC here:

        alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { (action: UIAlertAction) in returnToInitialVC() }

Anyway, good continuation and don't forget to close the thread on the correct answer.