iosswiftuitableviewuikituicontextmenuinteraction

UIContextMenu and UITableView issues


I'm trying to add a similar UIContextMenu as you can find in iMessages. When you long press a message, the context menu with some options should display. I use tableView(_:contextMenuConfigurationForRowAt:point:) and other methods. So far so good.

But I came to 2 problems which I'm not able to solve:

  1. the biggest one is the change of a preview. When the context menu is displayed and you receive a new message (which causes tableview to reload), the preview will change its content. So suddenly there is a different message than you originally selected. But I don't get it why because tableview methods for context menu aren't called... how could I resolve that? Apple Messages stops adding a new message. But for example Viber is still able to receive a new message while there is a context menu.

  2. I wanted to handle it somehow like Apple with the help of tableView(_:willDisplayContextMenu:animator:) ... but there is a second problem - this method is only for iOS +14.0.. ! So there is no way how I can I know that there will be context menu prior iOS 14?

I'll appreciate any help. Thanks.


Solution

  • I was able to solve the first problem somehow. The main idea is using snapshots instead of cell's view. This way even if tableView gets reload, the snapshot stays same.

    You have to implement these 2 methods and supply snapshot there: tableView(_:previewForHighlightingContextMenuWithConfiguration:) tableView(_:previewForDismissingContextMenuWithConfiguration:)

    In my code it looks like this:

    func tableView(_: UITableView, previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
            guard
                let messageId = configuration.identifier as? String,
                let indexPath = dataSource.indexPath(for: messageId),
                let cell = tableView.cellForRow(at: indexPath) as? YourCustomCell else {
                return nil
            }
    
            return makeTargetedPreview(cell: cell)
        }
    
     func makeTargetedPreview(cell: YourCustomCell) -> UITargetedPreview? {
            guard
                let previewView = cell.viewYouWantToDisplay,
                let snapshot = previewView.snapshotView(afterScreenUpdates: false)
            else {
                return nil
            }
    
            // 1. Prepare how should the view in the preview look like
            let parameters = UIPreviewParameters()
            parameters.backgroundColor = .clear
            parameters.visiblePath = UIBezierPath(roundedRect: previewView.bounds, cornerRadius: previewView.layer.cornerRadius)
    
            // 2. Prepare UIPreviewTarget so we can use snapshot
            let previewTarget = UIPreviewTarget(
                container: previewView,
                center: CGPoint(x: previewView.bounds.midX, y: previewView.bounds.midY)
            )
    
            // 3. Return UITargetedPreview with snapshot
            // important ! We can't use cell's previewView directly as it's getting changed when data reload happens
            return UITargetedPreview(view: snapshot, parameters: parameters, target: previewTarget)
        }
    

    Note: implementation of previewForDismissing(...) is similar.