I am using Realm Swift to build a todo list while learning iOS development. I'm running into a problem with my collectionView that happens when I try to re-order items using coordinator.drop(item.dragItem, toItemAt: destinationIndexPath)
.
When I include the coordinator
the dropped item first displays the contents of the cell that was originally at that index and then after a delay is replaced by the contents of the cell that was dragged. If I don't include the coordinator then the drag and drop works as intended and the item is correctly swapped when dropped (but the animation is that of a cell shrinking down to the center and a bit jarring).
When I reoder items in the collectionView:
func reorderItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) {
if let item = coordinator.items.first,
let sourceIndexPath = item.sourceIndexPath {
do {
try self.realm.write {
// standard category is of type Category
// items is of type List<Task>
standardCategory?.items.remove(at: sourceIndexPath.item)
standardCategory?.items.insert(item.dragItem.localObject as! Task, at: destinationIndexPath.item)
}
} catch {
print("error reording new items, \(error)")
}
coordinator.drop(item.dragItem, toItemAt: destinationIndexPath)
}
}
Inside of viewDidLoad() to handle updating the collectionView:
self.notificationToken = tasks!.observe {
(changes: RealmCollectionChange) in
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: { status in
self.collectionView.reloadData()
})
case .error(_):
print("error")
}
I think I have it working now. Assuming edge cases don't break it here's the answer:
I needed to handle the model updates and collectionView updates outside of tasks.observe so that I could control the order of events more exactly. You can do that by using try realm.commitWrite(withoutNotifying: [notificationToken!])
The key I think was to reorder the sequence of deleting and updating the items to: 1. delete from model 2. remove from collection view 3. inset into model 4. use coordinator to drop item 5. insert into collection view 6. reload section (optional I think, I needed it for UI changes)
func reorderItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) {
if let item = coordinator.items.first {
if let localObject = item.dragItem.localObject as? Task {
if let sourceIndexPath = item.sourceIndexPath {
do {
try self.realm.write {
standardCategory?.items.remove(at: sourceIndexPath.item)
try realm.commitWrite(withoutNotifying: [notificationToken!])
}
} catch { print("error reording new items, \(error)") }
collectionView.deleteItems(at: [sourceIndexPath])
}
do {
try self.realm.write {
standardCategory?.items.insert(localObject, at: destinationIndexPath.item)
try realm.commitWrite(withoutNotifying: [notificationToken!])
}
} catch { print("error reording new items, \(error)") }
self.collectionView.performBatchUpdates({
coordinator.drop(item.dragItem, toItemAt: destinationIndexPath)
collectionView.insertItems(at: [destinationIndexPath])
}, completion: { status in
let indexSet = IndexSet(integer: destinationIndexPath.section)
collectionView.reloadSections(indexSet)
})
}
}
}
SOLUTION: It turns out you can't use Results when sorting using drag and drop (not 100% sure why but that would be nice to know). Here's the current working solution using List instead:
func reorderItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) {
if let item = coordinator.items.first {
if let localObject = item.dragItem.localObject as? Task {
if let sourceIndexPath = item.sourceIndexPath {
self.collectionView.performBatchUpdates({
do {
standardCategory!.realm!.beginWrite()
standardCategory!.items.remove(at: sourceIndexPath.item)
collectionView.deleteItems(at: [sourceIndexPath])
self.standardCategory!.items.insert(localObject as Task, at: destinationIndexPath.item)
coordinator.drop(item.dragItem, toItemAt: destinationIndexPath)
collectionView.insertItems(at: [destinationIndexPath])
try standardCategory!.realm!.commitWrite(withoutNotifying: [notificationToken!])
} catch { print("error reording new items, \(error)") }
}, completion: nil)
}
}
}
}