Swift TableView删除行异常:Firestore数据排序与索引不匹配
Twitter克隆应用删除帖子索引不匹配问题解决
问题背景
开发Twitter克隆应用,已完成Firestore对接。从Firestore读取帖子时,通过timestamp字段排序获取有序数组,为让最新帖子显示在顶部,将新帖子插入数组索引0位置,导致数组索引与显示顺序反向(最底部的帖子对应数组索引0)。删除最新帖子时,实际删除的是最底部的帖子——虽然Firestore中已正确删除,刷新后显示恢复正常,但视觉层面存在错误,需要找到不依赖索引删除选中单元格的方案。
相关代码
Firestore数据监听代码
db.collection("Posts").document(doc.documentID).collection("Posts").order(by: "timestamp").addSnapshotListener { documentSnapshot, error in for docu in documentSnapshot!.documents { let daate = docu.data()["timestamp"] as! Timestamp let mydbl = daate.dateValue().timeIntervalSince1970 * 1000 let post = Post(id: docu.documentID, username: "@\(docu.data()["username"]!)", userId: doc.documentID , text: docu.data()["text"] as! String, timestamp: mydbl , numLikes: 0, numReposts: 0) let isDuplicate = tempPosts.contains(where: { $0.id == post.id }) if(!isDuplicate){ self.posts = tempPosts tempPosts.insert(post, at: 0) } } self.posts.removeAll() self.posts = tempPosts self.tableView.reloadData() }
TableView单元格配置与删除代码
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell cell.set(post: posts[indexPath.row]) cell.didDelete = { aCell in let currentIndexPath = tableView.indexPath(for: aCell)! self.posts.remove(at: currentIndexPath.row) print(currentIndexPath.row) // 删除最新帖子时打印0,但实际删除的是最底部帖子 tableView.deleteRows(at: [currentIndexPath], with: .automatic) } return cell }
单元格类代码
import UIKit import FirebaseAuth import FirebaseFirestore class PostTableViewCell: UITableViewCell, UITextViewDelegate{ @IBOutlet weak var profileImg: UIImageView! @IBOutlet public weak var username: UILabel! @IBOutlet weak var timestamp: UILabel! @IBOutlet weak var postText: UITextView! @IBOutlet weak var menuButton: UIButton! public var postId: String! var didDelete : ((UITableViewCell) -> Void)? override func awakeFromNib() { super.awakeFromNib() profileImg.layer.cornerRadius = profileImg.bounds.height / 2 profileImg.clipsToBounds = true postText.delegate = self } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) } func set(post: Post){ username.text = post.username postText.text = post.text timestamp.text = post.createdAt.calenderTimeSinceNow() postId = post.id setPopupMenu(post: post) } func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { if(text == "\n") { postText.resignFirstResponder() postText.isEditable = false postText.isScrollEnabled = false return false } return true } func setPopupMenu(post: Post){ let options = { (action: UIAction) in print(action.title) if(action.title == "تعديل"){ self.postText.isEditable = true self.postText.isScrollEnabled = true self.postText.becomeFirstResponder() } else if(action.title == "حذف"){ let db = Firestore.firestore() db.collection("Posts").document(Auth.auth().currentUser!.uid).collection("Posts").document(post.id).delete() { err in if let err = err { print("Error removing document: \(err)") } else { self.didDelete!(self) } } } } if(post.userId == Auth.auth().currentUser!.uid){ self.menuButton.menu = UIMenu(children: [ UIAction(title: "تعديل", image: UIImage(systemName: "pencil"), handler: options), UIAction(title: "حذف", image: UIImage(systemName: "trash"), attributes: .destructive, handler: options), ]) } else{ self.menuButton.menu = UIMenu(children: [ UIAction(title: "حظر", image: UIImage(systemName: "pencil"), handler: options), UIAction(title: "ميوت", image: UIImage(systemName: "trash"), attributes: .destructive, handler: options), ]) } menuButton.showsMenuAsPrimaryAction = true } }
解决方案
核心思路:通过帖子唯一ID定位删除,摆脱索引依赖
直接利用每个帖子的id(Firestore文档ID)作为唯一标识,在删除时通过ID找到数组中的对应元素,而非依赖单元格的indexPath。同时可以优化数据加载逻辑,从根源避免索引与显示顺序反向的问题。
具体修改步骤
1. 调整单元格的删除回调定义
将PostTableViewCell中的didDelete闭包改为传递帖子ID,而非单元格本身:
// 替换原有的didDelete定义 var didDelete : ((String) -> Void)?
2. 修改单元格中删除成功后的回调调用
在删除Firestore文档成功后,调用闭包时传入当前帖子的ID:
else if(action.title == "حذف"){ let db = Firestore.firestore() db.collection("Posts").document(Auth.auth().currentUser!.uid).collection("Posts").document(post.id).delete() { err in if let err = err { print("Error removing document: \(err)") } else { // 传入post.id而非self self.didDelete?(post.id) } } }
3. 更新TableView中的删除处理逻辑
收到帖子ID后,在数组中找到匹配的元素并删除,再更新TableView:
cell.didDelete = { postId in // 通过postId找到数组中的对应索引 if let index = self.posts.firstIndex(where: { $0.id == postId }) { self.posts.remove(at: index) tableView.deleteRows(at: [IndexPath(row: index, section: 0)], with: .automatic) } }
额外优化:修正Firestore数据加载逻辑
原代码中手动插入索引0的方式容易出错,可让Firestore直接返回降序排列的结果,无需手动调整顺序:
db.collection("Posts").document(doc.documentID).collection("Posts") .order(by: "timestamp", descending: true) // 添加descending参数,直接获取降序结果 .addSnapshotListener { documentSnapshot, error in guard let documents = documentSnapshot?.documents else { print("Error fetching documents: \(error!)") return } var newPosts = [Post]() for docu in documents { let daate = docu.data()["timestamp"] as! Timestamp let mydbl = daate.dateValue().timeIntervalSince1970 * 1000 let post = Post(id: docu.documentID, username: "@\(docu.data()["username"]!)", userId: doc.documentID , text: docu.data()["text"] as! String, timestamp: mydbl , numLikes: 0, numReposts: 0) newPosts.append(post) } // 直接赋值,最新帖子已在数组首位 self.posts = newPosts self.tableView.reloadData() }
内容的提问来源于stack exchange,提问作者Yousef Baghlaf
相关产品推荐
相关产品推荐

