使用Realm Swift的Results/List时,如何解决coordinator.drop延迟显示问题?
我之前在做Realm+UICollectionView的拖拽排序时,也碰到过一模一样的问题——用coordinator.drop时UI会有延迟显示,不用的话动画又特别生硬。本质原因是Realm的实时数据通知和CollectionView的拖拽动画发生了冲突,咱们一步步来解决:
问题根源分析
你当前的逻辑是:修改Realm数据 → 触发Realm通知 → CollectionView执行批量更新;同时又调用coordinator.drop让系统处理拖拽动画。这两个UI更新操作同时进行,就会导致CollectionView的渲染混乱,出现先显示原单元格内容、延迟后才替换的情况。
解决方案:拖拽时暂停Realm通知的UI处理
我们可以通过标记拖拽状态,让Realm通知在拖拽过程中暂时不处理UI更新,等拖拽结束后再同步数据,这样就能让coordinator.drop的动画流畅执行了。
步骤1:添加拖拽状态标记
在你的ViewController里添加一个布尔变量,用来标记当前是否处于拖拽状态:
private var isDragging = false private var notificationToken: NotificationToken?
步骤2:监听拖拽开始/结束事件
实现UICollectionViewDragDelegate的两个方法,更新拖拽状态,拖拽结束后手动同步一次UI:
extension YourViewController: UICollectionViewDragDelegate { func collectionView(_ collectionView: UICollectionView, dragSessionWillBegin session: UIDragSession) { isDragging = true } func collectionView(_ collectionView: UICollectionView, dragSessionDidEnd session: UIDragSession) { isDragging = false // 拖拽结束后手动刷新,确保Realm数据和UI完全同步 collectionView.reloadData() } }
步骤3:修改Realm通知回调
在Realm的监听回调里,判断如果正在拖拽,就跳过批量更新操作:
self.notificationToken = tasks!.observe { (changes: RealmCollectionChange) in // 拖拽过程中不处理Realm通知的UI更新,避免和拖拽动画冲突 guard !self.isDragging else { return } switch changes { case .initial(_): self.collectionView.reloadData() case .update(_, let deletions, let insertions, let modifications): self.collectionView.performBatchUpdates({ self.collectionView.deleteItems(at: deletions.map({ IndexPath(item: $0, section: 0) })) self.collectionView.insertItems(at: insertions.map({ IndexPath(item: $0, section: 0) })) self.collectionView.reloadItems(at: modifications.map({ IndexPath(item: $0, section: 0) })) }, completion: nil) // 移除原completion里的reloadData,避免重复刷新 case .error(let error): print("Realm observation error: \(error)") } }
步骤4:优化排序方法
确保在修改Realm数据后调用coordinator.drop,此时因为拖拽状态为true,Realm通知不会干扰动画:
func reorderItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) { guard let item = coordinator.items.first, let sourceIndexPath = item.sourceIndexPath, let task = item.dragItem.localObject as? Task, let category = standardCategory else { return } do { try self.realm.write { category.items.remove(at: sourceIndexPath.item) category.items.insert(task, at: destinationIndexPath.item) } // 让系统处理拖拽动画,此时无Realm通知干扰 coordinator.drop(item.dragItem, toItemAt: destinationIndexPath) } catch { print("Error reordering items: \(error)") } }
为什么这样有效?
通过在拖拽时暂停Realm通知的UI处理,我们把UI动画的控制权完全交给了UICollectionViewDropCoordinator,避免了两个UI更新操作的冲突。拖拽结束后再手动刷新一次,确保Realm的数据和UI完全同步,既解决了延迟问题,又保留了流畅的拖拽动画。
内容的提问来源于stack exchange,提问作者EmptyPockets

