Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Weird animation when deleting item from UICollectionView while UIContextMenu is shown

I'm using UIContextMenuInteraction to show a context menu for UICollectionView as follows:

func collectiovnView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
    return UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: { _ in
        let deleteAction = UIAction(title: "Delete", image: UIImage(systemName: "trash"), attributes: .destructive) { _ in
            self.deleteItem(at: indexPath)
        }
        return UIMenu(title: "Actions", children: [deleteAction])
    })
}

func deleteItem(at indexPath: IndexPath) {
    self.collectionView.performBatchUpdates({
        self.items.remove(at: indexPath.item)
        self.collectionView.deleteItems(at: [indexPath])
    })
}

Everything works well, but when I tap the "Delete" item, a weird animation happens where the deleted item stays in its place while other items are moving, and then it disappears instantly. And sometimes I even see an empty space or a random item for fraction of a second before the new item appears.

If I call collectionView.deleteItems() while the context menu isn't shown the deletion animation works as expected.

like image 477
Hejazi Avatar asked Sep 18 '19 16:09

Hejazi


1 Answers

It looks like the weird animation is a result of a conflict between two animations that run at almost the same time:

  1. Deletion animation: When "Delete" item is tapped, collectionView.deleteItems() is called and the specified collection item is deleted with animation.
  2. Menu dismiss animation: After the menu item is tapped, the context menu is also dismissed with another animation that shows the deleted item for a fraction of a second.

This looks like a bug that should be fixed by Apple. But as a workaround, I had to delay the deletion until the dismiss animation completed:

func deleteItem(at indexPath: IndexPath) {
    let delay = 0.4 // Seconds
    DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
        self.collectionView.performBatchUpdates({
            self.items.remove(at: indexPath.item)
            self.collectionView.deleteItems(at: [indexPath])
        })
    }
}

The 0.4 seconds is the shortest delay that worked for me.

like image 155
Hejazi Avatar answered Nov 16 '22 03:11

Hejazi