Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UITableView Destructive Context Menu Animation

Tags:

ios

uikit

swift

I'm adding iOS13 context menus to my table view. One of the menu actions allows the user to delete the item:

override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
    return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { suggestedActions in
       let deleteAction = UIAction(title: "Delete", image: UIImage(systemName: "trash.fill"), identifier: nil, discoverabilityTitle: "", attributes: UIMenuElement.Attributes.destructive) { action in
            self.data.remove(at: indexPath.row)

            //Remove from the table.
            self.tableView.deleteRows(at: [indexPath], with: .automatic)
        }

        return UIMenu(title: "", children: [deleteAction])
    }
}

I'm using the default preview view controller (so it just shows the cell). I'm currently seeing a weird animation artifact where the context menu preview is displayed while the items below the row being removed animated up, then the preview fades away to white (so it looks like there is a blank row in the list), then the table repaints and displays the item that was covered up. enter image description here

This is using the default cell but it looks a lot worse when using a customized cell with a lot more information. Is there anyway to make this action animate better?

like image 503
Runt8 Avatar asked Aug 10 '20 18:08

Runt8


Video Answer


1 Answers

I ran into this problem as well. The bug is probably due to the fact that the original cell used to generate the preview was deleted, moved or changed.

The solution I found was to implement the delegate method tableView(_:previewForHighlightingContextMenuWithConfiguration:), passing it the original cell as the view, but customising UIPreviewParameters to use UIColor.clear:

    override func tableView(_ tableView: UITableView, previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
        guard let indexPath = configuration.identifier as? IndexPath, let cell = tableView.cellForRow(at: indexPath) else {
            return nil
        }
        
        let parameters = UIPreviewParameters()
        parameters.backgroundColor = .clear
        
        return UITargetedPreview(view: cell, parameters: parameters)
    }

In order to identify the original cell in this delegate method, you'd need a way to identify it. One way is to set the indexPath as the identifier to UIContextMenuConfiguration, like:

    override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
        return UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: nil) { _ in
            return UIMenu(title: "", children: [
                UIAction(title: "Delete", image: UIImage(systemName: "trash"), attributes: .destructive) { action in
                    self.data.remove(at: indexPath.row)
                    tableView.deleteRows(at: [indexPath], with: .automatic)
                }
            ])
        }
    }

However if your data can change between the presentation of the context menu and the action, then you need a more robust way to identify it.

I did not have to implement tableView(_:previewForDismissingContextMenuWithConfiguration:) for this to work.

like image 123
junjie Avatar answered Nov 10 '22 22:11

junjie