Перезагрузка отдельных строк TableView при изменениях в документе - PullRequest
0 голосов
/ 25 января 2019

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

Я использую данные Firestore для заполнения TableView в Swift 4.2 с использованием прослушивателя снимков.Это прекрасно работает, если я не возражаю против полной перезагрузки TableView при каждом изменении документа, однако теперь я добавил анимации в ячейку, которые инициируют изменение значения статуса в документе, и моя нынешняя реализация tableView.reloadData () вызывает всеячейки для воспроизведения их анимации с любым изменением любого документа в коллекции.

Мне нужна помощь в понимании того, как реализовать reloadRows (at: [IndexPath]), используя .documentChanges с diff.type == .modified, чтобы перезагрузить только те строки, которые были изменены и потратили больше времени, чем я »Я хотел бы признать, пытаясь понять это.= /

Я попытался реализовать 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 голосов
/ 15 июля 2019

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

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, но это был самый ясный способ написать это.

0 голосов
/ 28 января 2019

Так что я сделал это, не зная 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...