How to hide UIContextMenuInteraction/UITargetedPreview black background?

3.5k Views Asked by At

I want to add UIContextMenuInteraction to a UIView with some transparent parts. When user interact with this view, iOS shows this view with a black background.

enter image description here

Is it possible to make those parts transparent or change the color?

3

There are 3 best solutions below

0
On BEST ANSWER

Finally I found the solution! You need to implement this function if you want to show a custom curved:

func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForHighlightingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
    let previewTarget = UIPreviewTarget(container: self.view, center: center)
    let previewParams = UIPreviewParameters()

    var topLeft : CGFloat = 8
    var topRight : CGFloat = 9
    var bottomRight : CGFloat = 10
    var bottomLeft : CGFloat = 11

    let topLeftRadius = CGSize(width: topLeft, height: topLeft)
    let topRightRadius = CGSize(width: topRight, height: topRight)
    let bottomLeftRadius = CGSize(width: bottomLeft, height: bottomLeft)
    let bottomRightRadius = CGSize(width: bottomRight, height: bottomRight)

    previewParams.visiblePath = UIBezierPath(shouldRoundRect: TheExactRectYouWantToShow, topLeftRadius: topLeftRadius, topRightRadius: topRightRadius, bottomLeftRadius: bottomLeftRadius, bottomRightRadius: bottomRightRadius)
    let targetView = UITargetedPreview(view: YourViewToShow, parameters: previewParams, target: previewTarget)
    return targetView
}

Here we are drawing a UIBezierPath around our custom shaped view. To do this you may need this extension too:

extension UIBezierPath {
convenience init(shouldRoundRect rect: CGRect, topLeftRadius: CGSize = .zero, topRightRadius: CGSize = .zero, bottomLeftRadius: CGSize = .zero, bottomRightRadius: CGSize = .zero){

    self.init()

    let path = CGMutablePath()

    let topLeft = rect.origin
    let topRight = CGPoint(x: rect.maxX, y: rect.minY)
    let bottomRight = CGPoint(x: rect.maxX, y: rect.maxY)
    let bottomLeft = CGPoint(x: rect.minX, y: rect.maxY)

    if topLeftRadius != .zero{
        path.move(to: CGPoint(x: topLeft.x+topLeftRadius.width, y: topLeft.y))
    } else {
        path.move(to: CGPoint(x: topLeft.x, y: topLeft.y))
    }

    if topRightRadius != .zero{
        path.addLine(to: CGPoint(x: topRight.x-topRightRadius.width, y: topRight.y))
        path.addCurve(to:  CGPoint(x: topRight.x, y: topRight.y+topRightRadius.height), control1: CGPoint(x: topRight.x, y: topRight.y), control2:CGPoint(x: topRight.x, y: topRight.y+topRightRadius.height))
    } else {
         path.addLine(to: CGPoint(x: topRight.x, y: topRight.y))
    }

    if bottomRightRadius != .zero{
        path.addLine(to: CGPoint(x: bottomRight.x, y: bottomRight.y-bottomRightRadius.height))
        path.addCurve(to: CGPoint(x: bottomRight.x-bottomRightRadius.width, y: bottomRight.y), control1: CGPoint(x: bottomRight.x, y: bottomRight.y), control2: CGPoint(x: bottomRight.x-bottomRightRadius.width, y: bottomRight.y))
    } else {
        path.addLine(to: CGPoint(x: bottomRight.x, y: bottomRight.y))
    }

    if bottomLeftRadius != .zero{
        path.addLine(to: CGPoint(x: bottomLeft.x+bottomLeftRadius.width, y: bottomLeft.y))
        path.addCurve(to: CGPoint(x: bottomLeft.x, y: bottomLeft.y-bottomLeftRadius.height), control1: CGPoint(x: bottomLeft.x, y: bottomLeft.y), control2: CGPoint(x: bottomLeft.x, y: bottomLeft.y-bottomLeftRadius.height))
    } else {
        path.addLine(to: CGPoint(x: bottomLeft.x, y: bottomLeft.y))
    }

    if topLeftRadius != .zero{
        path.addLine(to: CGPoint(x: topLeft.x, y: topLeft.y+topLeftRadius.height))
        path.addCurve(to: CGPoint(x: topLeft.x+topLeftRadius.width, y: topLeft.y) , control1: CGPoint(x: topLeft.x, y: topLeft.y) , control2: CGPoint(x: topLeft.x+topLeftRadius.width, y: topLeft.y))
    } else {
        path.addLine(to: CGPoint(x: topLeft.x, y: topLeft.y))
    }

    path.closeSubpath()
    cgPath = path
}

And this:

extension UIView {

func roundCorners(topLeft: CGFloat = 0, topRight: CGFloat = 0, bottomLeft: CGFloat = 0, bottomRight: CGFloat = 0) {//(topLeft: CGFloat, topRight: CGFloat, bottomLeft: CGFloat, bottomRight: CGFloat) {
    let topLeftRadius = CGSize(width: topLeft, height: topLeft)
    let topRightRadius = CGSize(width: topRight, height: topRight)
    let bottomLeftRadius = CGSize(width: bottomLeft, height: bottomLeft)
    let bottomRightRadius = CGSize(width: bottomRight, height: bottomRight)
    let maskPath = UIBezierPath(shouldRoundRect: bounds, topLeftRadius: topLeftRadius, topRightRadius: topRightRadius, bottomLeftRadius: bottomLeftRadius, bottomRightRadius: bottomRightRadius)
    let shape = CAShapeLayer()
    shape.path = maskPath.cgPath
    layer.mask = shape
}
}
0
On

Like MAGiGO said, you can use previewForHighlightingContextMenuWithConfiguration and previewForDismissingContextMenuWithConfiguration delegate methods to create your own UITargetedPreview and set its background color to clear.

func collectionView(_ collectionView: UICollectionView, previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview?
{
    guard let indexPath = configuration.identifier as? IndexPath,
          let cell = collectionView.cellForItem(at: indexPath)
          else
    {
        return nil
    }
    
    let targetedPreview = UITargetedPreview(view: cell)
    targetedPreview.parameters.backgroundColor = .clear
    return targetedPreview
}

func collectionView(_ collectionView: UICollectionView, previewForDismissingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview?
{
    guard let indexPath = configuration.identifier as? IndexPath,
          let cell = collectionView.cellForItem(at: indexPath)
          else
    {
        return nil
    }
    
    let targetedPreview = UITargetedPreview(view: cell)
    targetedPreview.parameters.backgroundColor = .clear
    return targetedPreview
}

func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration?
{ 
 return UIContextMenuConfiguration(identifier: NSIndexPath(item: indexPath.item, section: indexPath.section), previewProvider: nil) { elements -> UIMenu? in
 ....

Note: we are passing NSIndexPath as the identifier as it requires to conform to NSCopying protocol.

1
On

Simpler solution to change context menu background color

func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForHighlightingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
    let previewTarget = UIPreviewTarget(container: self, center: <#your-view>.center)
    let previewParams = UIPreviewParameters()
    previewParams.backgroundColor = .clear

    return UITargetedPreview(view: <#your-view>, parameters: previewParams, target: previewTarget)
}