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
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 toDispatchQueue.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 arrayuserArray
, **EXCEPT for the ** image URL inuser.profile_picture
, which will always be for the single user you reference inAssignValueToUserObject
.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.
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:
andnumberOfRows:
In this case, I assume you should have User’s Struct/Class something like this:
Try Doing It This Way:
Method:
Tableview’s
cellForRowAt:
Note:
AssignValueToUserObject()
will fill user object with some Data.reloadData()
is going to call, which will Reload Tableview & Populate Data in Cell with New Data.