Using UIMenuController in iOS 14 for UICollectionView?

659 Views Asked by At

There are some tutorials about how to use UIMenuController inside UICollectionView but in iOS 14 it is semideprecated:

// These methods provide support for copy/paste actions on cells.
// All three should be implemented if any are.
- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath API_DEPRECATED_WITH_REPLACEMENT("collectionView:contextMenuConfigurationForItemAtIndexPath:", ios(6.0, 13.0));
- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender API_DEPRECATED_WITH_REPLACEMENT("collectionView:contextMenuConfigurationForItemAtIndexPath:", ios(6.0, 13.0));
- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender API_DEPRECATED_WITH_REPLACEMENT("collectionView:contextMenuConfigurationForItemAtIndexPath:", ios(6.0, 13.0));

In the same time UIMenuController still exists in iOS 14. How to use it for cells now?

1

There are 1 best solutions below

1
iceboxi On

The UICollectionViewDelegate will not trigger those method you post after iOS 14. But we still can get FirstResponder which UIMenuController need from UIView. Hits. UICollectionView still is UIView

Full Code:

protocol Menuable {
    func menuAction(_ sender: Any?)
}

class CustomCell: UICollectionViewCell, Menuable {
...
    func menuAction(_ sender: Any?){...}
...
}

class MenuableCollectionView: UICollectionView {
    var selectedIndexPath: IndexPath?
    
    func setupLongpress() {
        let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressGesture(_:)))
        self.addGestureRecognizer(longPressGesture)
    }

    func setupMenu() {
        let menuItem = UIMenuItem(title: "action", action: #selector(menuAction(_:)))
        UIMenuController.shared.menuItems = [menuItem]
        UIMenuController.shared.update()
    } 
    
    @objc func handleLongPressGesture(_ sender: UILongPressGestureRecognizer) {
        let point = sender.location(in: self)
        if sender.state == .began {
            guard let indexPath = indexPathForItem(at: point) else {
                return
            }
            
            selectedIndexPath = indexPath
            becomeFirstResponder()
            
            let cell = cellForItem(at: indexPath)
            
            let x: CGFloat = cell?.frame.midX ?? point.x
            let y: CGFloat = 10
            
            let rect = CGRect(x: x, y: y, width: 10, height: 10)
            if #available(iOS 13.0, *) {
                UIMenuController.shared.showMenu(from: self, rect: rect)
            } else {
                UIMenuController.shared.setTargetRect(rect, in: self)
                UIMenuController.shared.setMenuVisible(true, animated: true)
            }
        }
    }
    
    @objc func menuAction(_ sender: Any?) {
        guard let indexPath = selectedIndexPath else {
            return
        }
        let cell = cellForItem(at: indexPath) as? Menuable
        cell?.menuAction(sender)
    }
    
    override var canBecomeFirstResponder: Bool {
        return true
    }
    
    override open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
        if action == #selector(menuAction(_:)) {
            return true
        } else {
            return false
        }
    }
}

// you should call setupLongpress() and setupMenu() after collection view init.