在文档更改后重新加载各个TableView行

时间:2019-01-25 19:36:14

标签: swift google-cloud-firestore

长时间的监听器,初次开发应用程序。

我正在使用Firestore数据通过快照侦听器在Swift 4.2中填充TableView。如果我不介意每次文档更改都重新加载整个TableView,这将非常有用,但是现在我已向单元格中添加了动画,这些动画在文档中的状态值更改时触发,而我目前的tableView.reloadData()实现会触发所有动画。对集合中任何文档进行任何更改都可以播放其动画的单元。

我正在寻求帮助,以了解如何使用具有diff.type == .modified的.documentChanges来实现reloadRows(at:[IndexPath]),以仅重新加载已更改且花费了更多时间的行。承认试图弄清楚。 = /

我尝试实现tableView.reloadRows,但是似乎无法理解如何仅为需要更新的行正确指定indexPath。也许我需要为动画添加条件逻辑,以便仅在文档更改时执行动画?脱掉头发。任何帮助都将不胜感激。

快照实施:

    self.listener = query?.addSnapshotListener(includeMetadataChanges: true) { documents, error in

        guard let snapshot = documents else {

            print("Error fetching snapshots: \(error!)")

            return

        }

        snapshot.documentChanges.forEach { diff in

            if (diff.type == .added) {

                let source = snapshot.metadata.isFromCache ? "local cache" : "server"

                print("Metadata: Data fetched from \(source)")

                let results = snapshot.documents.map { (document) -> Task in

                    if let task = Task(eventDictionary: document.data(), id: document.documentID) {

                        return task

                    } // if

                    else {

                        fatalError("Unable to initialize type \(Task.self) with dictionary \(document.data())")

                    } // else

                } //let results

                self.tasks = results

                self.documents = snapshot.documents

                self.tableView.reloadData()

            } // if added

            if (diff.type == .modified) {

                print("Modified document: \(diff.document.data())")

                let results = snapshot.documents.map { (document) -> Task in

                    if let task = Task(eventDictionary: document.data(), id: document.documentID) {

                        return task

                    } // if

                    else {

                        fatalError("Unable to initialize type \(Task.self) with dictionary \(document.data())")

                    } // else closure

                } //let closure

                self.tasks = results

                self.documents = snapshot.documents

                self.tableView.reloadData() // <--- reloads the entire tableView with changes = no good

                self.tableView.reloadRows(at: <#T##[IndexPath]#>, with: <#T##UITableView.RowAnimation#>) // <-- is what I need help with

            }

            if (diff.type == .removed) {

                print("Document removed: \(diff.document.data())")

            } // if removed

        } // forEach

    } // listener

cellForRowAt

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {


    let cell = tableView.dequeueReusableCell(withIdentifier: "eventListCell", for: indexPath) as! EventTableViewCell

    let item = tasks[indexPath.row]

    let url = URL.init(string: (item.eventImageURL))
    datas.eventImageURL = url

    cell.eventImageView.kf.setImage(with: url)

    cell.eventEntranceLabel!.text = item.eventLocation

    cell.eventTimeLabel!.text = item.eventTime

    if item.eventStatus == "inProgress" {


        cell.eventReponderStatus.isHidden = false

        cell.eventReponderStatus.text = "\(item.eventResponder)" + " is responding"


        UIView.animate(withDuration: 2, delay: 0.0, options: [.allowUserInteraction], animations: {cell.backgroundColor = UIColor.yellow; cell.backgroundColor = UIColor.white}, completion: nil)

    }

    else if item.eventStatus == "verifiedOK" {

        cell.eventReponderStatus.isHidden = false

        cell.eventReponderStatus.text = "\(item.eventResponder)" + " verified OK"

        UIView.animate(withDuration: 2, delay: 0.0, options: [.allowUserInteraction], animations: {cell.backgroundColor = UIColor.green; cell.backgroundColor = UIColor.white}, completion: nil)

    }

    else if item.eventStatus == "sendBackup" {

        cell.eventReponderStatus.isHidden = false

        cell.eventReponderStatus.text = "\(item.eventResponder)" + " needs assistance"

        UIView.animate(withDuration: 1, delay: 0.0, options: [.repeat, .autoreverse, .allowUserInteraction], animations: {cell.backgroundColor = UIColor.red; cell.backgroundColor = UIColor.white}, completion: nil)
    }

    else if item.eventStatus == "newEvent" {


        UIView.animate(withDuration: 2, delay: 0.0, options: [.allowUserInteraction], animations: {cell.backgroundColor = UIColor.red; cell.backgroundColor = UIColor.white}, completion: nil)



    }

    else {
        cell.isHidden = true
        cell.eventReponderStatus.isHidden = true

    }


    switch item.eventStatus {

    case "unhandled": cell.eventStatusIndicator.backgroundColor = UIColor.red

    case "inProgress": cell.eventStatusIndicator.backgroundColor = UIColor.yellow

    case "verifiedOK": cell.eventStatusIndicator.backgroundColor = UIColor.green

    case "sendBackup": cell.eventStatusIndicator.backgroundColor = UIColor.red


    default: cell.eventStatusIndicator.backgroundColor = UIColor.red

    }

    return cell
}

变量和设置

// Create documents dictionary
private var documents: [DocumentSnapshot] = []

// Create tasks var
public var tasks: [Task] = []

// Create listener registration var
private var listener : ListenerRegistration!

// Create baseQuery function
fileprivate func baseQuery() -> Query {

    switch switchIndex {
    case 0:
        return Firestore.firestore().collection("metalDetectorData").document("alarmEvents").collection("eventList").limit(to: 50).whereField("eventStatus", isEqualTo: "unhandled")
    case 1:
        return Firestore.firestore().collection("metalDetectorData").document("alarmEvents").collection("eventList").limit(to: 50).whereField("eventStatus", isEqualTo: "verifiedOK")
    case 3:
        return Firestore.firestore().collection("metalDetectorData").document("alarmEvents").collection("eventList").limit(to: 50)
    default:
        return Firestore.firestore().collection("metalDetectorData").document("alarmEvents").collection("eventList").limit(to: 50)//.whereField("eventStatus", isEqualTo: false)
    }

} // baseQuery closure



// Create query variable
fileprivate var query: Query? {
    didSet {
        if let listener = listener {
            listener.remove()

        }
    }
} // query closure

任务

struct Task{

    var eventLocation: String
    var eventStatus: String
    var eventTime: String
    var eventImageURL: String
    var eventResponder: String
    var eventUID: String

    var eventDictionary: [String: Any] {
        return [
            "eventLocation": eventLocation,
            "eventStatus": eventStatus,
            "eventTime": eventTime,
            "eventImageURL": eventImageURL,
            "eventResponder": eventResponder,
            "eventUID": eventUID
            ]
    } // eventDictionary


} // Task

extension Task{
    init?(eventDictionary: [String : Any], id: String) {
        guard let eventLocation = eventDictionary["eventLocation"] as? String,
              let eventStatus = eventDictionary["eventStatus"] as? String,
              let eventTime = eventDictionary["eventTime"] as? String,
              let eventImageURL = eventDictionary["eventImageURL"] as? String,
              let eventResponder = eventDictionary["eventResponder"] as? String,
              let eventUID = id as? String

            else { return nil }

        self.init(eventLocation: eventLocation, eventStatus: eventStatus, eventTime: eventTime, eventImageURL: eventImageURL, eventResponder: eventResponder, eventUID: eventUID)


    }
}

2 个答案:

答案 0 :(得分:0)

因此,我这样做的时候并没有真正了解Firebase,也没有编译器来检查错误。可能会有一些错别字,您可能需要进行一些解包和转换,但是这个想法应该存在。我添加了很多注释,以帮助您了解代码中正在发生的事情……

self.listener = query?.addSnapshotListener(includeMetadataChanges: true) { documents, error in

    guard let snapshot = documents else {

        print("Error fetching snapshots: \(error!)")

        return

    }

    // You only need to do this bit once, not for every update
    let source = snapshot.metadata.isFromCache ? "local cache" : "server"

    print("Metadata: Data fetched from \(source)")

    let results = snapshot.documents.map { (document) -> Task in

        if let task = Task(eventDictionary: document.data(), id: document.documentID) {

            return task

        } // if

        else {

            fatalError("Unable to initialize type \(Task.self) with dictionary \(document.data())")

        } // else

    } //let results

    // Tell the table view you are about to give it a bunch of updates that should all get batched together
    self.tableView.beginUpdates()

    snapshot.documentChanges.forEach { diff in

        let section = 0 // This should be whatever section the update is in. If you only have one section then 0 is right.

        if (diff.type == .added) {
            // If a document has been added we need to insert a row for it…
            // First we filter the results from above to find the task connected to the document ID.
            // We use results here because the document doesn't exist in tasks yet.
            let filteredResults = results.filter { $0.eventUID == diff.document.documentID }
            // Do some saftey checks on the filtered results
            if filteredResults.isEmpty {
                // Deal with the fact that there is a document that doesn't have a companion task in results. This shouldn't happen, but who knows…
            }
            if filteredResults.count > 1 {
                // Deal with the fact that either the document IDs aren't terribly unique or that a task was added more than once for the saem document
            }
            let row = results.index(of: filteredResults[0])
            let indexPath = IndexPath(row: row, section: section)

            // Tell the table view to insert the row
            self.tableView.insertRows(at: [indexPath], with: .fade)

        } // if added

        if (diff.type == .modified) {
            // For modifications we need to get the index out of tasks so the index path matches the current path not the one it will have after the updates.
            let filteredTasks = self.tasks.filter { $0.eventUID == diff.document.documentID }
            // Do some saftey checks on the filtered results
            if filteredTasks.isEmpty {
                // Deal with the fact that there is a document that doesn't have a companion task in results. This shouldn't happen, but who knows…
            }
            if filteredTasks.count > 1 {
                // Deal with the fact that either the document IDs aren't terribly unique or that a task was added more than once for the saem document
            }
           let row = self.tasks.index(of: filteredTasks[0])
            let indexPath = IndexPath(row: row, section: section)

        // Tell the table view to update the row
        self.tableView.reloadRows(at: [indexPath], with: .fade)

    }

    if (diff.type == .removed) {

        print("Document removed: \(diff.document.data())")

        // For deleted documents we need to use tasks since it doesn't appear in results
        let filteredTasks = self.tasks.filter { $0.eventUID == diff.document.documentID }
        // Do some saftey checks on the filtered results
        if filteredTasks.isEmpty {
            // Deal with the fact that there is a document that doesn't have a companion task in results. This shouldn't happen, but who knows…
        }
        if filteredTasks.count > 1 {
            // Deal with the fact that either the document IDs aren't terribly unique or that a task was added more than once for the saem document
        }
        let row = self.tasks.index(of: filteredTasks[0])
        let indexPath = IndexPath(row: row, section: section)
        // ** Notice that the above few lines are very similiar in all three cases. The only thing that varies is our use results or self.tasks. You can refactor this out into its own method that takes the array to be filtered and the documentID you are looking for. It could then return either the the row number by itself or the whole index path (returning just the row would be more flexible).

        // Tell the table view to remove the row
        self.tableView.deleteRows(at: [indexPath], with: .fade)

    } // if removed

    } // forEach

    // Sync tasks and documents with the new info
    self.tasks = results

    self.documents = snapshot.documents

    // Tell the table view you are done with the updates so It can make all the changes
     self.tableView.endUpdates()

} // listener

答案 1 :(得分:0)

在变更监听器中,您真正需要做的就是保存相应变更的索引,保存模型对象,然后触发表视图更新。

let insertions = snapshot.documentChanges.compactMap {
    return $0.type == .added ? IndexPath(row: Int($0.newIndex), section: 0) : nil
}
let modifications = snapshot.documentChanges.compactMap {
    return $0.type == .modified ? IndexPath(row: Int($0.newIndex), section: 0) : nil
}
let deletions = snapshot.documentChanges.compactMap {
    return $0.type == .removed ? IndexPath(row: Int($0.oldIndex), section: 0) : nil
}

self.userDocuments = snapshot.documents
self.tableView.beginUpdates()
self.tableView.insertRows(at: insertions, with: .automatic)
self.tableView.reloadRows(at: modifications, with: .automatic)
self.tableView.deleteRows(at: deletions, with: .automatic)
self.tableView.endUpdates()

有更有效的方式将更改映射到IndexPaths,但这是编写更改的最清晰的方法。