如何检索 firestore 数据并从字典中填充 Tableview Rows/Sections?

How to retrieve firestore data and populate Tableview Rows/Sections from a dictionary?

我正在尝试使用我已经解析并存储在字典中的 Firestore 数据填充我的表视图的部分和行,看起来像这样...

dataDict = ["Monday": ["Chest", "Arms"], "Wednsday": ["Legs", "Arms"], "Tuesday": ["Back"]]

坦率地说,我什至不确定是否应该像我那样将数据存储在字典中。这样做是错误的吗?此外,由于数据是异步提取的,我如何才能仅在 字典完全加载我的网络数据后填充我的部分和行?我正在使用完成处理程序,但是当我尝试打印 dataDict 的结果时,它会连续打印出三个数组,就像这样...

["Monday": ["Chest", "Arms"]]
["Tuesday": ["Back"], "Monday": ["Chest", "Arms"]]
["Tuesday": ["Back"], "Monday": ["Chest", "Arms"], "Wednsday": ["Legs", "Arms"]]

而我希望它在完成后 return 阵列的单个打印。我做错了什么?

var dataDict : [String:[String]] = [:]


//MARK: - viewDidLoad()
override func viewDidLoad() {
    super.viewDidLoad()

    vcBackgroundImg()
    navConAcc()

    picker.delegate = self
    picker.dataSource = self

    tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellID)
    tableView.tableFooterView = UIView()

    Auth.auth().addStateDidChangeListener { (auth, user) in
        self.userIdRef = user!.uid
        self.colRef = Firestore.firestore().collection("/users/\(self.userIdRef)/Days")

        self.loadData { (done) in
            if done {
                print(self.dataDict)
            } else {
                print("Error retrieving data")
            }
        }

    }

}

//MARK: - Load Data
func loadData(completion: @escaping (Bool) -> ()){
        self.colRef.getDocuments { (snapshot, err) in
            if let err = err
            {
                print("Error getting documents: \(err)");
                completion(false)
            }
            else {
                //Appending all Days collection documents with a field of "dow" to daysarray...
                for dayDocument in snapshot!.documents {
                    self.daysArray.append(dayDocument.data()["dow"] as? String ?? "")
                    self.dayIdArray.append(dayDocument.documentID)


                    Firestore.firestore().collection("/users/\(self.userIdRef)/Days/\(dayDocument.documentID)/Workouts/").getDocuments { (snapshot, err) in
                        if let err = err
                        {
                            print("Error getting documents: \(err)");
                            completion(false)
                        }
                        else {
                            //Assigning all Workouts collection documents belonging to selected \(dayDocument.documentID) to dictionary dataDict...
                            for document in snapshot!.documents {

                                if self.dataDict[dayDocument.data()["dow"] as? String ?? ""] == nil {
                                    self.dataDict[dayDocument.data()["dow"] as? String ?? ""] = [document.data()["workout"] as? String ?? ""]
                                } else {
                                    self.dataDict[dayDocument.data()["dow"] as? String ?? ""]?.append(document.data()["workout"] as? String ?? "")
                                }
                                DispatchQueue.main.async {
                                    self.tableView.reloadData()
                                }
                                // print(self.dataDict)
                            }
                            completion(true)
                        }
                    }
                }
                self.dayCount =  snapshot?.count ?? 0
            }
        }
    }

我认为这只是你程序的流程。每次循环遍历集合时,都会将它得到的内容添加到字典中。所以在第一次通过时,它会打印字典有 1 个项目。在第二遍中,它向字典中添加了另一个项目,然后打印了现在有 2 个项目的字典,因此打印了 2 个项目。我认为您没有看到意外的行为,这只是您如何根据循环方式对日志语句进行排序。

换句话说,这样打印是有道理的。

我同意@ewizard 的回答。问题出在您的程序流程中。您遍历文档并为每次迭代获取集合中的文档。您还多次重新加载 tableView 和调用完成闭包,这是您不想做的。

为了改善您的程序流程,请尝试使用 DispatchGroup 来获取您的数据,然后在获取所有数据后将它们放在一起一次。请参阅下面的示例以了解基本概念。我的示例是您代码的一个非常简化的版本,我想向您展示您应该执行的重要更改。

func loadData(completion: @escaping (Bool) -> ()) {
    self.colRef.getDocuments { (snapshot, err) in
        // Handle error

        let group = DispatchGroup()
        var fetchedData = [Any]()

        // Iterate through the documents
        for dayDocument in snapshot!.documents {
            // Enter group
            group.enter()
            // Fetch data
            Firestore.firestore().collection("/users/\(self.userIdRef)/Days/\(dayDocument.documentID)/Workouts/").getDocuments { (snapshot, err) in
                // Add your data to fetched data here
                fetchedData.append(snapshot)
                // Leave group
                group.leave()
            }
        }

        // Waits for until all data fetches are finished
        group.notify(queue: .main) {
            // Here you can manipulate fetched data and prepare the data source for your table view
            print(fetchedData)

            // Reload table view and call completion only once
            self.tableView.reloadData()
            completion(true)
        }
    }
}

我也同意其他评论,您应该重新考虑 tableView 的数据模型。更合适的结构是二维数组(数组的数组 - 第一个数组转换为 table 视图部分,内部数组对象转换为部分项目)。 这是一个例子:

// Table view data source
[
    // Section 0
    [day0, day1],
    // Section 1
    [day2, day3],
    // Section 2
    [day4, day5],
]

用法示例:

extension ViewController: UITableViewDataSource {

    func numberOfSections(in tableView: UITableView) -> Int {
        return sections.count
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return sections[section].count
    }

}