长时间的监听器,初次开发应用程序。
我正在使用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)
}
}
答案 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,但这是编写更改的最清晰的方法。