Home > Enterprise >  Customize context menu item highlight color in SwiftUI
Customize context menu item highlight color in SwiftUI

Time:01-11

I have a button whose image has some additional clear space around it. The highlight when tapped regularly looks fine, and doesn't effect the clear space. However, when holding down to bring up the context menu, a light gray highlight is applied to the entire image area, which looks bad.

Is there a way to customize this highlight so the color is clear?

One possibility that I've tried but doesn't work:

.contentShape(.contextMenuPreview, Circle().size(width: 0, height: 0))

The problem here is that it causes the entire view to disappear during the context menu animation and visibility. I want to change the color of the highlight to clear, while maintaining the small resize animation and of course the visibility of the button.

iPhone simulator screenshot showing incorrect context menu highlight

CodePudding user response:

SwiftUI doesn't offer an option to remove the background but with its UIKit counterpart UIContextMenuInteraction you can implement

func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configuration: UIContextMenuConfiguration, highlightPreviewForItemWithIdentifier identifier: NSCopying) -> UITargetedPreview? 

And implement your own preview. It can look something like below.

func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configuration: UIContextMenuConfiguration, highlightPreviewForItemWithIdentifier identifier: NSCopying) -> UITargetedPreview? {
    print(#function)
    
    interaction.view?.backgroundColor = .clear
    let previewTarget = UIPreviewTarget(container: interaction.view!, center: interaction.view!.center)
    
    let previewParams = UIPreviewParameters()
    
    previewParams.backgroundColor = .clear
    
    return .init(view: interaction.view!, parameters: previewParams, target: previewTarget)
}

You will get something like this with that code.

enter image description here

Below you will find a full implementation

struct CustomContextMenuView: View {
    var body: some View {
        
        Image(systemName: "person")
            .resizable()
            .frame(width: 100, height: 100)
        
            .contextMenu(actions: [
                UIAction(title: "Add to Favorites", image: UIImage(systemName: "heart.fill"), handler: { a in
                    print("Add to Favorites action")
                    
                })
            ], willEnd:  {
                //Called when the menu is dismissed
                print("willEnd/onDismiss")
                
            }, willDisplay:  {
                //Called when the menu appears
                print("willDisplay/onAppear")
                
            })
        
        
    }
}

extension View {
    func contextMenu(actions: [UIAction], willEnd: (() -> Void)? = nil, willDisplay: (() -> Void)? = nil) -> some View {
        modifier(ContextMenuViewModifier(actions: actions, willEnd: willEnd, willDisplay: willDisplay))
    }
}
struct ContextMenuViewModifier: ViewModifier {
    let actions: [UIAction]
    let willEnd: (() -> Void)?
    let willDisplay: (() -> Void)?
    func body(content: Content) -> some View {
        Interaction_UI(view: {content}, actions: actions, willEnd: willEnd, willDisplay: willDisplay)
            .fixedSize()
        
    }
}

struct Interaction_UI<Content2: View>: UIViewRepresentable{
    typealias UIViewControllerType = UIView
    @ViewBuilder var view: Content2
    let actions: [UIAction]
    let willEnd: (() -> Void)?
    let willDisplay: (() -> Void)?
    func makeCoordinator() -> Coordinator {
        return Coordinator(parent: self)
    }
    func makeUIView(context: Context) -> some UIView {
        let v = UIHostingController(rootView: view).view!
        context.coordinator.contextMenu = UIContextMenuInteraction(delegate: context.coordinator)
        v.addInteraction(context.coordinator.contextMenu!)
        return v
    }
    
    func updateUIView(_ uiView: UIViewType, context: Context) {
        
    }
    class Coordinator: NSObject,  UIContextMenuInteractionDelegate{
        var contextMenu: UIContextMenuInteraction!
        
        let parent: Interaction_UI
        
        init(parent: Interaction_UI) {
            self.parent = parent
        }
        func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
            UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: { [self]
                suggestedActions in
                
                return UIMenu(title: "", children: parent.actions)
            })
        }
        func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willDisplayMenuFor configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating?) {
            print(#function)
            parent.willDisplay?()
        }
        func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willEndFor configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating?) {
            print(#function)
            parent.willEnd?()
            
        }
        func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configuration: UIContextMenuConfiguration, highlightPreviewForItemWithIdentifier identifier: NSCopying) -> UITargetedPreview? {
            print(#function)
            
            let previewParams = UIPreviewParameters()
            previewParams.backgroundColor = .clear
            return UITargetedPreview(view: interaction.view!, parameters: previewParams)
        }
    }
}
struct CustomContextMenuView_Previews: PreviewProvider {
    static var previews: some View {
        CustomContextMenuView()
    }
}
  • Related