skip to Main Content

This question may be asked because of my ignorance regarding completion and how to use them properly, but I have been researching and just couldn’t find a solution to my problem.

I have a function that gets user data from database, I then want to assign values that I got from the database to my table view cell. If I don’t have completion in my AssignValueToUserObject method, my table view cell is called before I actually get the values which results in an error.

So I found that completion was the way to go and it was working for me so far, but now that I am using table view cell, I can’t actually call the table view method manually inside the completion, so I don’t know how I can get the data using the method, and then assign the values to the table view cell.

Here is my method:

extension MoneyGroupViewController {

public func AssignValueToUserObject(completion: @escaping () -> Void) {
    // Get user unique id
    guard let uid = Auth.auth().currentUser?.uid else {
        print("Could not get user id")
        return
    }

    Database.database().reference().child("users").child(uid).observeSingleEvent(of: .value, with: { [self] snapshot in
        if let dictionary = snapshot.value as? [String: AnyObject] {
            
            user.first_name = dictionary["first_name"] as! String
            user.last_name = dictionary["last_name"] as! String
            user.email = dictionary["email"] as! String
            user.profile_picture = dictionary["profile_picture"] as? String
            
            completion() //call once the action is done
        }
    }, withCancel: nil)
} // End AssignValueToUserObject Method
} // End extension

And here is my table view cell function which is my view controller:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = groupTableView.dequeueReusableCell(withIdentifier: "MoneyGroupTableViewCell", for: indexPath) as! MoneyGroupTableViewCell
    
    cell.userName.text = userArray[indexPath.row].first_name + " " + userArray[indexPath.row].last_name
    
    if (user.profile_picture != "") {
        let url = URL(string: user.profile_picture!)!
        
        URLSession.shared.dataTask(with: url, completionHandler: { data, _, error in
            DispatchQueue.main.async {
                cell.userImage.layer.cornerRadius = max(cell.userImage.frame.width,  cell.userImage.frame.height)/2
                cell.userImage.layer.borderWidth = 1
                cell.userImage.image = UIImage(data: data!)
            }
        }).resume() // End URLSession
    }
    
    return cell
}

And finally, here is how I am calling the method:

override func viewDidLoad() {
    super.viewDidLoad()
    
    AssignValueToUserObject {
        // I should call the table view function here but I don't know how
    }
   }

Update

Yes my cell should display user’s info (first name, last name, email, and an optional profile picture).

I potentially want to add all of users that are in a "group" in my userArray, but right now I am not implementing that functionality, and I just have the same user in userArray to test my table view.

So userArray should only have the same user right now, but my problem still persists, I don’t know how I can get user’s info from the database before the table view cell call.

2

Answers


  1. You are thinking about this wrong.

    You should not try to assign things to your table view cells. You should save your newly fetched data to your model, and then tell your table view that it needs to update. If you have loaded new content for an existing cell, use reloadRows(at:with:) to tell the table view to reload the cell for that data. The table view will then ask it’s data source for an updated cell. The data source will build the updated cell from your updated data, and it will display correctly.

    Note that if you call reloadRows(at:with:) from inside your completion handler, you should wrap the call in a call to DispatchQueue.main.async() so that it is executed on the main thread.

    Edit

    Looking at your code, you have a number of problems.

    What is your table view supposed to display? Cells with info about users? (first and last name, email address, and profile picture?

    What does your userArray contain? An array of structs of what type? UserInfo? edit your question to include that information.

    It looks like you actually have 2 separate async operations to complete before you are able to display everything about a user: You need to do a database fetch to get info about the user (including the URL to their profile picture, and then you need to make a network call (to URLSession.shared.dataTask to download your image.

    Your AssignValueToUserObject function appears to always read info for a single user, but your code that configures table view cells seems to fetch data from an array userArray, **EXCEPT for the ** image URL in user.profile_picture, which will always be for the single user you reference in AssignValueToUserObject.

    In short, your code is pretty jumbled up, and without a clear description of what you are trying to do, we can’t help you un-jumble it.

    Login or Signup to reply.
  2. You don’t need to use Completion Block here.

    Simply use UITableView’s instance Method reloadData() whenever you need your tableview to reload its contents.
    This could be when your data model changes.

    Calling tableView.reloadData() will run all the datasource methods including:

    cellForItemAt: and numberOfRows:

    In this case, I assume you should have User’s Struct/Class something like this:

    struct UserData
    {
        var first_name : String = ""
        var last_name : String = ""
        var email : String = ""
        var profile_picture : String = ""
    }
    

    Try Doing It This Way:

    class YourClass : UIViewController {
    
    //Create an Empty User (As you want to implement with single user first)
    let user : UserData = UserData()
    
    override func viewDidLoad() {
            super.viewDidLoad()
    
       AssignValueToUserObject()
    
        }
    

    Method:

    extension MoneyGroupViewController {
    
    public func AssignValueToUserObject() {
        // Get user unique id
        guard let uid = Auth.auth().currentUser?.uid else {
            print("Could not get user id")
            return
        }
    
        Database.database().reference().child("users").child(uid).observeSingleEvent(of: .value, with: { [weak self] snapshot in
            
            if let dictionary = snapshot.value as? [String: AnyObject] {
                user.first_name = dictionary["first_name"] as! String
                user.last_name = dictionary["last_name"] as! String
                user.email = dictionary["email"] as! String
                user.profile_picture = dictionary["profile_picture"] as? String
            }
    
            //Reload Your Tableview once you're done Fetching Data from Firestore
            DispatchQueue.main.async {
            self?.YOURTABLEVIEW.reloadData()
         }
        }, withCancel: nil)
     } // End AssignValueToUserObject Method
    }
    

    Tableview’s cellForRowAt:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = groupTableView.dequeueReusableCell(withIdentifier: "MoneyGroupTableViewCell", for: indexPath) as! MoneyGroupTableViewCell
        
        cell.userName.text = user.first_name + " " + user.last_name
      
        //Note: You should use "SDWebImage" or "Kingfisher" Library for async image downloading
        if let url = URL(string: user.profile_picture)
        {
            URLSession.shared.dataTask(with: url, completionHandler: { data, _, error in
                DispatchQueue.main.async {
                    cell.userImage.layer.cornerRadius = max(cell.userImage.frame.width,  cell.userImage.frame.height)/2
                    cell.userImage.layer.borderWidth = 1
                    cell.userImage.image = UIImage(data: data!)
                }
            }).resume() // End URLSession
        }
        return cell
    }
    

    Note:

    1. At first Tableview is going to Load but with Empty Data. (Eg: Empty user)
    2. AssignValueToUserObject() will fill user object with some Data.
    3. reloadData() is going to call, which will Reload Tableview & Populate Data in Cell with New Data.
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search