Как присвоить значение ячейке представления таблицы в обработчике завершения Swift

# #ios #swift #xcode #firebase

Вопрос:

Этот вопрос может быть задан из-за моего незнания относительно завершения и того, как их правильно использовать, но я провел исследование и просто не смог найти решение своей проблемы.

У меня есть функция, которая получает пользовательские данные из базы данных, затем я хочу присвоить значения, полученные из базы данных, моей ячейке представления таблицы. Если у меня нет завершения в моем методе AssignValueToUserObject, моя ячейка представления таблицы вызывается до того, как я фактически получу значения, что приводит к ошибке.

Итак, я обнаружил, что завершение было правильным решением, и до сих пор оно работало для меня, но теперь, когда я использую ячейку представления таблицы, я фактически не могу вызвать метод представления таблицы вручную внутри завершения, поэтому я не знаю, как я могу получить данные с помощью метода, а затем присвоить значения ячейке представления таблицы.

Вот мой метод:

 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
 

А вот моя функция ячейки представления таблицы, которая является моим контроллером представления:

 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
}
 

И, наконец, вот как я вызываю метод:

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

Обновить

Да, мой сотовый должен отображать информацию о пользователе (имя, фамилия, адрес электронной почты и необязательную фотографию профиля).

Я потенциально хочу добавить всех пользователей, которые находятся в «группе» в моем списке пользователей, но сейчас я не реализую эту функцию, и у меня просто есть один и тот же пользователь в списке пользователей, чтобы проверить мое представление таблицы.

Таким образом, у userArray должен быть только один и тот же пользователь прямо сейчас, но моя проблема все еще сохраняется, я не знаю, как я могу получить информацию о пользователе из базы данных до вызова ячейки представления таблицы.

Комментарии:

1. Покажите свое определение userArray и то, как вы его заполняете. Короткий ответ заключается в том, что вы не можете извлечь из своей базы данных или загрузить изображение до того, как представление таблицы будет готово к отображению. Вам необходимо отобразить пустое представление таблицы или ячейки с информацией о заполнителях, пока вы не сможете загрузить необходимые данные.

2. Если ваша цель-отобразить список пользователей и их информацию, не записывайте его сначала для работы с одним пользователем, а затем вернитесь и измените его для управления несколькими пользователями позже. Есть некоторые проблемы, связанные со списком пользователей, которые укусят вас позже, если вы это сделаете. В конечном итоге вам понадобится полная переписка.

3. Я понимаю, о чем вы говорите, я приму ваш совет и сначала попытаюсь управлять несколькими пользователями. Итак, как бы я реализовал отображение пустого представления таблицы или ячеек с заполнителем до тех пор, пока я не загружу данные? Вы не могли бы опубликовать ответ, который показывает это?

4. Firebase имеет несколько различных служб баз данных. Таким образом, использование тега firebase не указывает нам, какой сервис вы используете.

5. Я не уверен, о каких различных услугах вы говорите, но я использую базу данных в реальном времени.

Ответ №1:

Вы думаете об этом неправильно.

Вы не должны пытаться назначать объекты ячейкам представления таблицы. Вы должны сохранить только что полученные данные в своей модели, а затем сообщить своему табличному представлению, что оно нуждается в обновлении. Если вы загрузили новое содержимое для существующей ячейки, используйте reloadRows(at:with:) для указания табличному представлению перезагрузить ячейку для этих данных. Затем представление таблицы запросит у источника данных обновленную ячейку. Источник данных построит обновленную ячейку на основе ваших обновленных данных, и она будет отображаться правильно.

Обратите внимание, что если вы вызываете reloadRows(at:with:) из обработчика завершения, вы должны обернуть вызов в вызов DispatchQueue.main.async() , чтобы он выполнялся в основном потоке.

Редактировать

Глядя на ваш код, у вас возникает ряд проблем.

Что должно отображаться в вашем представлении таблицы? Ячейки с информацией о пользователях? (имя и фамилия, адрес электронной почты и фотография профиля?

Что содержит ваш пользовательский массив? Массив структур какого типа? Информация о пользователе? отредактируйте свой вопрос, чтобы включить эту информацию.

Похоже, что на самом деле вам нужно выполнить 2 отдельные асинхронные операции, прежде чем вы сможете отобразить все о пользователе: вам нужно выполнить выборку базы данных, чтобы получить информацию о пользователе (включая URL-адрес их изображения профиля), а затем вам нужно позвонить по сети ( URLSession.shared.dataTask чтобы загрузить свое изображение.

Ваша AssignValueToUserObject функция, похоже, всегда считывает информацию для одного пользователя , но ваш код, который настраивает ячейки представления таблицы, похоже, извлекает данные из массива userArray , **ЗА исключением URL-адреса изображения** user.profile_picture , который всегда будет для одного пользователя, на которого вы ссылаетесь AssignValueToUserObject .

Короче говоря, ваш код довольно запутан, и без четкого описания того, что вы пытаетесь сделать, мы не можем помочь вам разобраться в нем.

Комментарии:

1. Хорошо, не могли бы вы, пожалуйста, показать мне, как я мог бы сохранить данные в своей модели? Это тот бит, с которым я борюсь, должен ли я сделать это внутри завершения в методе AssignValueToUserObject или где-то еще?

2. Смотрите правку к моему ответу. Вам нужно предоставить гораздо больше информации, прежде чем мы сможем вам помочь (и, пожалуйста, отредактируйте свой вопрос, чтобы уточнить, а не добавлять комментарии).

3. Готово, дайте мне знать, если вам понадобится дополнительная информация.

Ответ №2:

Здесь вам не нужно использовать Блок завершения.

Просто используйте метод экземпляра UITableView reloadData() всякий раз, когда вам нужно, чтобы ваш tableview перезагрузил его содержимое. Это может произойти, когда ваша модель данных изменится.

Вызов tableView.reloadData() приведет к запуску всех методов источника данных, включая:

cellForItemAt: и numberOfRows:

В этом случае я предполагаю, что у вас должна быть структура/класс пользователя примерно так:

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

Попробуйте Сделать Это Таким Образом:

 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()

    }
 

Метод:

 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
}
 

Просмотр таблицы 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
}
 

Примечание:

  1. Сначала будет загружаться Tableview, но с пустыми данными. (Например: Пустой пользователь)
  2. AssignValueToUserObject() заполнит объект пользователя некоторыми данными.
  3. reloadData() собирается позвонить, который перезагрузит Tableview и заполнит данные в ячейке новыми данными.

Комментарии:

1. этот ответ очень близок к тому, что я хочу, но когда я его реализую, я сначала получаю имя пользователя без изображения его профиля, а через 2 секунды моя ячейка заполняется случайным полем, а все остальное исчезает. Вы знаете, почему у меня эта проблема?

2. Я думаю, что некоторые ограничения отсутствуют в вашем просмотре изображений внутри ячейки просмотра таблиц. Попробуйте использовать фиксированную высоту и ширину для просмотра изображений, это должно работать!

3. Вызов reloadData() работает, но это излишне и приведет к вялому взаимодействию с пользователем. Это приводит к перезагрузке каждой ячейки в табличном представлении. вам гораздо лучше перезагрузить конкретные ячейки, которые в этом нуждаются, используя reloadRows(at:with:) (и передавая массив выражений, которые нуждаются в обновлении).