I have a UIButton with a UIMenu associated with it. Upon clicking on button showing the UIMenu. But I can observe that button action which is causing to show Menu is calling only once, after that every subsequent click button action is not triggering. May be UIMenu is caching the button action in such a way that, particular button action call only once. But as per my requirement button action has to trigger every time on button click.

Below I am posting the sample code where I am trying to print the Random number on console every time user clicks on menu option and I can see every time same number is getting print. This is because this piece of code calling only first time let randomNumber = Int(arc4random_uniform(10))

*import UIKit class ViewController: UIViewController {

var name = String()
var namesText = ["one", "Two", "Three", "Four", "Five", "six", "Seven","Eight", "Nine", "Ten"]

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
}

@IBAction func buttonNewAction(_ sender: UIButton) {
        self.showMenu(sender: sender)
}

func showMenu(sender: UIButton) {
    let randomNumber = Int(arc4random_uniform(10))
        
    let usersItem = UIAction(title: "Users", image: UIImage(systemName: "person.fill")) { (action) in
        print("\(randomNumber) in User action")
    }
    
    let addUserItem = UIAction(title: "Add User", image: UIImage(systemName: "person.badge.plus")) { (action) in
        print("\(randomNumber) in add user action")
    }
    
    let removeUserItem = UIAction(title: "Remove User", image: UIImage(systemName: "person.fill.xmark.rtl")) { (action) in
        print("\(randomNumber) in Remove user")
    }
    
    let menu = UIMenu(title: "", children: [usersItem, addUserItem, removeUserItem])
    sender.menu = menu
    sender.showsMenuAsPrimaryAction = true
}

}*

1

There are 1 answers

4
son On BEST ANSWER

You can't re-assign menus for button by subclass UIButton. Then override UIControl delegate contextMenuInteraction willEnd to remove UIMenu property every time the menu popover is dismissed.

Updated: When you're expecting UIMenu always be setup when dismissed. I did a trick to handle callback when remove UIMenu from subclass of Button, then setup tapping action again because .menuActionTriggered just works once.

class MenuButton: UIButton {
    var onRemovedMenu: (() -> Void)?

    override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willEndFor configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating?) {
        super.contextMenuInteraction(interaction, willEndFor: configuration, animator: animator)
        removeMenuWhenDismissed()
    }
    
    private func removeMenuWhenDismissed() {
        self.menu = nil
        onRemovedMenu?()
    }
}

class MyViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let button = MenuButton()
        button.showsMenuAsPrimaryAction = true
        button.onRemovedMenu = { [weak self] in
            //put delay to avoid warning log when dismissing and adding menu too close
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {                
                self?.setupButton(button)
            }
        }
        
        button.setTitle("TAP ME", for: .normal)
        button.setTitleColor(.blue, for: .normal)
        setupButton(button)
        
        view.addSubview(button)
        button.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
        button.center = view.center
    }

    private func setupButton(_ button: MenuButton) {
        button.menu = UIMenu(title: "")
        //UIControler.Event to `menuActionTriggered`
        button.addTarget(self, action: #selector(self.showMenu), for: .menuActionTriggered)
    }
    ...
}

Final result:

enter image description here