Изменение в firebase не обновляет таблицу — таблицы не отображают пользовательские узлы из массивов

#arrays #swift #uitableview #firebase-realtime-database #refresh

# #массивы #swift #uitableview #firebase-база данных в реальном времени #обновить

Вопрос:

Я пытаюсь иметь два массива, используемых в качестве источников данных для двух табличных представлений. Один массив содержит тип пользователя: macro, а другой массив содержит тип пользователя: micro.

Если тип пользователя изменен с макро на микро (и наоборот) Я хочу удалить пользователя из одной таблицы и добавить его в другую.

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

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

Вот код для моего пользовательского класса:

 import UIKit
import Firebase

    class User: NSObject {
        var Name: String?
        var Email: String?
        var UID: String?
        var Tag: String?

init?(from snapshot: DataSnapshot) {

        let dictionary = snapshot.value as? [String: Any]
    
        self.Name = dictionary!["Name"] as? String
        self.Email = dictionary!["Email"] as? String
        self.UID = dictionary!["UID"] as? String
        self.Tag = dictionary!["Tag"] as? String
        
        }
}
 

Вот мой код для загрузки и заполнения моего allUsersArray информация добавляется:

 func loadAllUsersAndPopulateArray() {
    let ref = Database.database().reference().child("Users")
    ref.observeSingleEvent(of: .value, with: { snapshot in
        let allUsersSnapshot = snapshot.children.allObjects as! [DataSnapshot]
        for userSnap in allUsersSnapshot {
            let user = User(from: userSnap)
            self.allUsersArray.append(user!)
            
            self.macroUsersArray = self.allUsersArray.filter { $0.Tag == "Macro" }
            self.microUsersArray = self.allUsersArray.filter { $0.Tag == "Micro" }

            self.observeChangeInUserProperty()
        }
    })
}
 

Вот мой код для наблюдения за изменениями в Firebase:

  func observeChangeInUserProperty() {
    let ref = Database.database().reference().child("Users")
    ref.observe(.childChanged, with: { snapshot in
        let key = snapshot.key

        let tag = snapshot.childSnapshot(forPath: "Tag").value as! String // ! = never optional
        //get the user from the allUsersArray by its key
        if let user = self.allUsersArray.first(where: { $0.Tag == key }) {
            if user.Tag != tag { //if the tag changed, handle it
                user.Tag = tag //update the allUsersArray
                if tag == "Macro" { //if the new tag is Macro remove the user from the Micro array
                    if let userIndex = self.microUsersArray.firstIndex(where: { $0.Tag == key }) {
                        self.microUsersArray.remove(at: userIndex)
                        self.macroUsersArray.append(user) //add user to macro array
                    }
                } else { //new type is micro so remove from macro array
                    if let userIndex = self.macroUsersArray.firstIndex(where: { $0.Tag == key }) {
                        self.macroUsersArray.remove(at: userIndex)
                        self.microUsersArray.append(user)
                    }
                }
                //reload the tableviews to reflect the changes
                self.tableView.reloadData()
                self.microTableView.reloadData()

            }
        }
    })
}
 

Вот мой код для контроллера:

 class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

var allUsersArray = [User]()
var macroUsersArray = [User]()
var microUsersArray = [User]()

override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.delegate = self
        tableView.dataSource = self
        searchBar.delegate = self
        tableView.register(UserCell.self, forCellReuseIdentifier: networkCell)
        
        microTableView.delegate = self
        microTableView.dataSource = self
        microTableView.register(microCell.self, forCellReuseIdentifier: microCell)
        
        loadAllUsersAndPopulateArray()
        
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        if (tableView === self.tableView) {
        
            return 1
            
          }


        else if (tableView === self.microTableView) {
            // Do something else
        
            return 1
        
        }
        
        fatalError("Invalid table")
        
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        if (tableView === self.tableView) {
            
                let cell = tableView.dequeueReusableCell(withIdentifier: networkCell, for: indexPath) as! UserCell
            
                let user = macroUsersArray[indexPath.row]
                cell.textLabel?.text = user.Name
                    
                    return cell
                    
                } else if (tableView === self.microTableView) {
                    
                    let cell = tableView.dequeueReusableCell(withIdentifier: microCell, for: indexPath) as! microInfluencerCell
                
                    let user = microUsersArray[indexPath.row]
                    cell.textLabel?.text = user.Name
                
                        return cell
            
        } else {
            fatalError("Invalid table")
        }
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        if (tableView === self.tableView) {
            return macroUsersArray.count
        }
        else if (tableView === self.microTableView) {
            return microUsersArray.count
        }
        
        else {
            fatalError("Invalid table")
        }

    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        
        if (tableView === self.tableView) {
            return 72
        }
        else if (tableView === self.microTableView) {
            return 72
        }
        
        else {
            fatalError("Invalid table")
        }
    }

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    
    if (tableView === self.tableView) {
        
        dismiss(animated: true) {
            let userName = self.macroUsersArray[indexPath.row]
            self.showSecondViewController(user: userName)

            print("Dismiss completed")
        }
        
    }
    else if (tableView === self.microTableView) {
        
        dismiss(animated: true) {
            let userName = self.microUsersArray[indexPath.row]
            self.showSecondController(user: userName)

            print("Dismiss completed")
        }
        
    }
    
    else {
        fatalError("Invalid table")
    }
  }
}

func showSecondViewController(user: User) {

    let SecondViewController = SecondViewController()
    SecondViewController.user = user
    let navigationController = UINavigationController(rootViewController: SecondViewController)
    self.present(navigationController, animated: true, completion: nil)
  }

func loadAllUsersAndPopulateArray() {
let ref = Database.database().reference().child("Users")
ref.observeSingleEvent(of: .value, with: { snapshot in
    let allUsersSnapshot = snapshot.children.allObjects as! [DataSnapshot]
    for userSnap in allUsersSnapshot {
        let user = User(from: userSnap)
        self.allUsersArray.append(user!)

        self.macroUsersArray = self.allUsersArray.filter { $0.Tag == "Macro" }
        self.microUsersArray = self.allUsersArray.filter { $0.Tag == "Micro" }

        self.observeChangeInUserProperty()
     }
   })
 }
func observeChangeInUserProperty() {
let ref = Database.database().reference().child("Users")
ref.observe(.childChanged, with: { snapshot in
    let key = snapshot.key

    let tag = snapshot.childSnapshot(forPath: "Tag").value as! String // ! = never optional
    //get the user from the allUsersArray by its key
    if let user = self.allUsersArray.first(where: { $0.Tag == key }) {
        if user.Tag != tag { //if the tag changed, handle it
            user.Tag = tag //update the allUsersArray
            if tag == "Macro" { //if the new tag is Macro remove the user from the Micro array
                if let userIndex = self.microUsersArray.firstIndex(where: { $0.Tag == key }) {
                    self.microUsersArray.remove(at: userIndex)
                    self.macroUsersArray.append(user) //add user to macro array
                }
            } else { //new type is micro so remove from macro array
                if let userIndex = self.macroUsersArray.firstIndex(where: { $0.Tag == key }) {
                    self.macroUsersArray.remove(at: userIndex)
                    self.microUsersArray.append(user)
                }
            }
            //reload the tableviews to reflect the changes
            self.tableView.reloadData()
            self.microTableView.reloadData()

        }
       }
    })
  }
}
 

Я так близок к решению этой проблемы. Любая помощь была бы замечательной.

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

 func loadAllUsersAndPopulateArray() {
    let ref = Database.database().reference().child("Users").child("Talent")
    ref.observeSingleEvent(of: .value, with: { snapshot in
        let allUsersSnapshot = snapshot.children.allObjects as! [DataSnapshot]
        for userSnap in allUsersSnapshot {
            let user = User(from: userSnap)
            self.allUsersArray.append(user!)
            self.allUsersNames.append(user!.Name!)

        }

        self.observeChangeInUserProperty()
    })

    self.macroUsersArray = self.allUsersArray.filter { $0.Tag == "Macro" }
    self.microUsersArray = self.allUsersArray.filter { $0.Tag == "Micro" }
    
    self.tableView.reloadData()
    self.microTableView.reloadData()
    
}
 

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

1. У вас есть код в неправильном месте. При заполнении allUsersArray for userSnap in allUsersSnapshot вам не нужно продолжать вызывать self.macroUsersArray или self.observeChange повторять снова и снова для каждого пользователя. Сначала заполните allUsersArray, затем сразу после этого цикла заполните макро- и микромассивы и добавьте наблюдателя. И в качестве рекомендации по устранению неполадок временно исключите Firebase из уравнения и создайте некоторые фиктивные тестовые данные в качестве источника (ов) данных TableView, чтобы убедиться, что просмотры таблиц и ячейки действительно работают правильно.

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

3. Вам действительно нужно выполнить некоторые действия по устранению неполадок, чтобы понять, что не так. Добавьте точки останова и пройдитесь по своему коду — посмотрите func tableView(_ tableView: UITableView, numberOfRowsInSection , возвращает ли он правильное число строк. Затем проверьте func tableView(_ tableView: UITableView, cellForRowAt и посмотрите, заполняется ли ячейка допустимыми строками и т.д.

4. Итак, я устранил некоторые проблемы, и за пределами функции loadAllUsers массив allusers не добавляется. Я проверил внутри моментального снимка, но вне функции массивы пусты

5. Вы должны обновить код в вопросе в loadAllUsersAndPopulateArray функции в соответствии с моим комментарием выше, поскольку он все еще неверен. Что касается allUsersArray того, что вы пусты вне этой функции, у вас на руках загадка. Он был бы пустым только в том случае, если ViewController вышел из области видимости, был вызван несколько раз (так что вы указываете на другой экземпляр, чем вы думали) или если вы удаляете содержимое. Я подозреваю, что это может иметь какое-то отношение к ViewController, поскольку вызов этого var allUsersArray = [User]() создает пустой массив. Добавьте точки останова, пройдитесь по коду.

Ответ №1:

С помощью Джея я смог найти решение!

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

Спасибо @Jay за всю помощь!!!

вот новый код функции:

 func loadAllUsersAndPopulateArray() {
    let ref = Database.database().reference().child("Users")
    ref.observeSingleEvent(of: .value, with: { snapshot in
        let allUsersSnapshot = snapshot.children.allObjects as! [DataSnapshot]
        for userSnap in allUsersSnapshot {
            let user = User(from: userSnap)
            self.allUsersArray.append(user!)
            self.allUsersNames.append(user!.Name!)

            self.macroUsersArray = self.allUsersArray.filter { $0.Tag == "Macro" }
            self.microUsersArray = self.allUsersArray.filter { $0.Tag == "Micro" }

            self.tableView.reloadData()
            self.microTableView.reloadData()
            
        }

        self.observeChangeInUserProperty()
    })
    
 

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

1. Вы должны изменить это в соответствии с инструкциями, которые я предоставил в комментарии. Как есть, для каждого пользователя вы переназначаете и фильтруете массив снова и снова. Это много лишних потраченных впустую циклов процессора. Наряду с этим повторная перезагрузка TableView приведет к его мерцанию. Переместите это self.macroUsersArray и self.microUsersArray вместе с перезагрузкой TableView после цикла for.

Ответ №2:

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

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

Кроме того, поскольку вы заполняете массив данными из firebase и других массивов, основанных на первом, нет причин фильтровать их снова и снова; заполните основной массив, а затем, когда это будет сделано, заполните другие массивы.

Наконец, просмотр таблиц чувствителен, и их повторная загрузка может вызвать мерцание. Опять же, заполните свой основной массив, и как только это будет сделано, а другие массивы заполнены, перезагрузите tableViews — один раз.

Вот как должен выглядеть этот раздел кода

 func loadAllUsersAndPopulateArray() {
    let ref = Database.database().reference().child("Users")
    ref.observeSingleEvent(of: .value, with: { snapshot in
        let allUsersSnapshot = snapshot.children.allObjects as! [DataSnapshot]
        for userSnap in allUsersSnapshot {
            let user = User(from: userSnap)
            self.allUsersArray.append(user!)
            self.allUsersNames.append(user!.Name!)            
        }

        self.macroUsersArray = self.allUsersArray.filter { $0.Tag == "Macro" }
        self.microUsersArray = self.allUsersArray.filter { $0.Tag == "Micro" }

        self.tableView.reloadData()
        self.microTableView.reloadData()

        self.observeChangeInUserProperty()
    })
}
 

Я также предложу избавиться от allUsersNames массива, поскольку эти имена также хранятся в allUsersArray .

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

1. к сожалению, когда я перемещаю filters и table.reloadData() за пределы того места, где они у меня есть, таблицы не заполняются, а массивы остаются пустыми. Когда я настраиваю его с помощью вашего кода, мои массивы остаются пустыми, и таблица не заполняется моими пользователями.

2. @NewCoder Тогда у вас что-то еще не так или это (tableView === self.tableView) вызывает проблему, поскольку в этом, вероятно, нет необходимости. На самом деле это не совпадает с вашими другими функциями делегирования, поэтому вы можете переосмыслить, как они кодируются.