In iOS 14 I have configured a button to display a menu of questions. On its own the button is working perfectly. Here’s how it’s configured:
let button = UIButton()
button.setImage(UIImage(systemName: "chevron.down"), for: .normal)
button.showsMenuAsPrimaryAction = true
button.menu = self.menu
I have it set on a text field’s rightView. This is how it looks when the menu is displayed.
When a menu item is tapped, that question appears in a label just above the text field. It works great. Except….
I also want the menu to appear when the user taps in the text field. I don’t want them to have to tap on the button specifically. In the old days of target-action this was easy: button.sendActions(for: .touchUpInside)
. I tried that and .menuActionTriggered
and .primaryActionTriggered
but none of those work. I think sendActions() is looking for the old selector based actions.
But there’s a new sendActions(for: UIControl.Event) method. Here’s what I’m trying in textFieldShouldBeginEditing()
:
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
let tempAction = UIAction { action in
print("sent action")
}
menuButton.sendAction(tempAction)
print("textField tapped")
return false
}
Sure enough, “sent action” and “textField tapped” appear in the console when the text field is tapped. But I have no idea what UIAction I could send that means “display your menu”. I wish there was this method: send(_: UIControl.Event)
.
Any ideas on how to get the button to display its menu when the text field is tapped?
ps. yes, textFieldShouldBeginEditing
will need to know if a question has been selected and in that case will need to allow editing. That part is easy.
From what I read, you cannot programmatically trigger
UIContextMenuInteraction
as interactions seem to be handled internally by itself unlikesend(_: UIControl.Event)
I see this mentioned on this SO post here
Also in the docs it seems that we don't have access to interaction management Apple needs to decide 3D touch is available or default back to long tap
From the docs
A context menu interaction object tracks Force Touch gestures on devices that support 3D Touch, and long-press gestures on devices that don't support it.
Workaround
I can propose the following workaround for your use case. My example was created using frame instead of auto layout as faster and easier to demo the concept this way, however you will need to make adjustments using autolayout
1. Create the UI elements
2. Regular setup of the label and the text view first
3. Add the button to the text view
4. End Result
Start with a similar view to yours
Then I tap in the middle of the text field
It displays the menu as intended
After selecting an option, the question shows in the label and the placeholder updates
Now I can start typing my answer using the text field and the button is only active on the right side since it was resized
And the button is still active
Final thoughts
Edits from OP (too long for a comment):
contentEdgeInsets
so the button was way over to the left but it covered up the placeholder text.rightView
. Then it would accept a tap even if the textField was the first responder.