iosswiftswiftuiuiviewuikit

Custom context menu corner radius


I created my custom context menu using UIView. Im trying to change the corner radius when the context menu is active, but I have no idea how. I tried masks, layer whatsoever, but nothing worked. The provided current code is not rounding the corners. When I tried to set the background color to clear, then in between the fuzzy color and the content appeared a color that is the background color. Any idea?

Here is the undesired behavior (you can see the distinct corner rounding): enter image description here

    import SwiftUI

struct CustomContextMenu<Content: View>: View {
    var content: Content
    var menu: UIMenu?
    var previewBackgroundColor: UIColor
    
    init(
        @ViewBuilder content: @escaping ()->Content,
        actions: @escaping ()-> UIMenu?,
        previewBackgroundColor: UIColor
    ) {
        self.content = content()
        self.menu = actions()
        self.previewBackgroundColor = previewBackgroundColor
    }
    
    var body: some View {
        content
            .hidden()
            .overlay(
                ContextMenuHelper(content: content, actions: menu, previewBackgroundColor: previewBackgroundColor)
            )
    }
}

struct ContextMenuHelper<Content: View>: UIViewRepresentable
{
    
    var content: Content
    var actions: UIMenu?
    var previewBackgroundColor: UIColor
    
    
    
    
    init(content: Content, actions: UIMenu?, previewBackgroundColor: UIColor) {
        self.content = content
        self.actions = actions
        self.previewBackgroundColor = previewBackgroundColor
    }
    
    
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(parent: self )
    }
    
    
    
    func makeUIView(context: Context) -> UIView {
        // uiview
        let view = UIView()
        view.backgroundColor = UIColor.clear
        view.layer.cornerRadius = 20
        
        // host view
        let hostView = UIHostingController(rootView: content)
        hostView.view.translatesAutoresizingMaskIntoConstraints = false
        hostView.view.backgroundColor = .clear
        view.addSubview(hostView.view) // add subview
        view.addConstraints( // add constraints
            [
                hostView.view.topAnchor.constraint(equalTo: view.topAnchor),
                hostView.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                hostView.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                hostView.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
                
                hostView.view.widthAnchor.constraint(equalTo: view.widthAnchor),
                hostView.view.heightAnchor.constraint(equalTo: view.heightAnchor)
            ]
        )
        
        if self.actions != nil { // if any menu item has been loaded
            // interaction
            let interaction = UIContextMenuInteraction(delegate: context.coordinator)
            view.addInteraction(interaction)
        }
        
        return view
    }
    
    
    
    func updateUIView(_ uiView: UIView, context: Context) {
        
    }
    
    
    
    class Coordinator: NSObject, UIContextMenuInteractionDelegate {
        var parent: ContextMenuHelper
        init(parent: ContextMenuHelper) {
            self.parent = parent
        }
        
        func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
            interaction.view?.layer.cornerRadius = 30
            guard let viewFrame = interaction.view?.frame else { return nil } // obtain child view size
            return UIContextMenuConfiguration(identifier: nil) {
                let previewController = UIHostingController(rootView: self.parent.content)
                
                previewController.view.backgroundColor = self.parent.previewBackgroundColor
                previewController.preferredContentSize = CGSize(width: viewFrame.width, height: viewFrame.height)
                
                return previewController
            } actionProvider: { items in
                return self.parent.actions
            }
        }
    }
}

EDIT:

When Im wrapping the view into another view it changes the inner view, not the outer view:

enter image description here

 func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
            interaction.view?.layer.cornerRadius = 30
            guard let viewFrame = interaction.view?.frame else { return nil } // obtain child view size
            return UIContextMenuConfiguration(identifier: nil) {
                let previewView = self.parent.content
                    .background(Color(self.parent.previewBackgroundColor).edgesIgnoringSafeArea(.all))
                    .cornerRadius(30)

                
                let previewController = UIHostingController(rootView: previewView)
                
                previewController.preferredContentSize = CGSize(width: viewFrame.width, height: viewFrame.height)
                
                return previewController
            } actionProvider: { items in
                return self.parent.actions
            }
        }

Solution

  • So I resolved the issue by changing contextMenuInteraction method contents and by rounding view which wraps host view.

    struct ContextMenuHelper<Content: View>: UIViewRepresentable
    {
        
        var content: Content
        var actions: UIMenu?
        var previewBackgroundColor: UIColor
        
        
        
        
        init(content: Content, actions: UIMenu?, previewBackgroundColor: UIColor) {
            self.content = content
            self.actions = actions
            self.previewBackgroundColor = previewBackgroundColor
        }
        
        
        
        func makeCoordinator() -> Coordinator {
            return Coordinator(parent: self )
        }
        
        
        
        func makeUIView(context: Context) -> UIView {
            // uiview
            let view = UIView()
            view.layer.cornerRadius = 20
            
            // host view
            let hostView = UIHostingController(rootView: content)
            hostView.view.translatesAutoresizingMaskIntoConstraints = false
            hostView.view.backgroundColor = .clear
            view.addSubview(hostView.view) // add subview
            view.addConstraints( // add constraints
                [
                    hostView.view.topAnchor.constraint(equalTo: view.topAnchor),
                    hostView.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                    hostView.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                    hostView.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
                    
                    hostView.view.widthAnchor.constraint(equalTo: view.widthAnchor),
                    hostView.view.heightAnchor.constraint(equalTo: view.heightAnchor)
                ]
            )
            
            if self.actions != nil { // if any menu item has been loaded
                // interaction
                let interaction = UIContextMenuInteraction(delegate: context.coordinator)
                view.addInteraction(interaction)
            }
            
            return view
        }
        
        
        
        func updateUIView(_ uiView: UIView, context: Context) {
            
        }
        
        
        
        class Coordinator: NSObject, UIContextMenuInteractionDelegate {
            var parent: ContextMenuHelper
            init(parent: ContextMenuHelper) {
                self.parent = parent
            }
            
            func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
                let configuration = UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { suggestedActions in
                    return self.parent.actions
                }
                
                return configuration
            }
        }
    }