Search code examples
swiftuitextviewios13uimenucontroller

Another UIMenuController won't display problem


and thanks in advance for your help. I assure you that I've read most everything on here about UIMenuController problems. I really think I've covered it all. Clearly I've missed something.

In a nutshell, I'm trying to replicate the "Replace ..." edit menu behavior (but with my own function different than Replace). (If you're not familiar, when a word is selected, the Replace... option in the edit menu will bring up a second menu which shows possible alternate spellings for the word.)

In a UITextView (sub-classed), I select some text. The default gesture recognizer causes the edit menu to come up with the expected items, including my added "Translate..." option. When I click on "Translate..." in the menu, the menu closes and invokes my selector code. That code changes the menu items to the sub-choices I want. I call UIMenuController.shared.showMenu(from: self, rect: textBounds). I see the calls to canPerformAction() to verify that the "sub-menu" items I've added are recognized, but the menu never shows up. The notification for willShowWindowNotification (which occurs when the first menu is opened) does not happen for this sub-menu.

Here is the code:

@objc func translateSelectionMenu()
{
    let sharedMC = UIMenuController.shared
    // Create menu choices for the translate sub-menu.
    let charChoice = UIMenuItem(title: "To Chars", action: #selector(translateChars))
    let byteChoice = UIMenuItem(title: "Byte Decimal", action: #selector(translateByte))
    let halfChoice = UIMenuItem(title: "2-Byte Decimal", action: #selector(translateHalf))
    savedMenuItems = sharedMC.menuItems
    sharedMC.menuItems = [charChoice, byteChoice, halfChoice]

... for brevity, I've omitted the code here which determines the bounds of the user's
    text selection. The resulting numbers are shown below.

    let textBounds = CGRect(x: 114.1, y: 73, width: 48, height: 55) 
    // let windowBounds = convert(textBounds, to: nil)
    // sharedMC.update() not needed
    self.becomeFirstResponder()  // TextView is already the first responder. This does nothing.
    sharedMC.showMenu(from: self, rect: textBounds)
}

Note that the TextView IS and must remain first-responder. (Changing it loses the users selection.) So I've implemented all of this in the subclass of the UITextView that is showing the user's text. I have tried using the UITextView-referenced bounds and the window-referenced bounds but neither works.

If I move one of the end-points of the selected text or just click in the selection, this causes the menu to be shown again, and it has my sub-menu items in it as expected. I know this should work because "Replace..." does it all the time.

Things I've verified:

  1. My sub-class of UITextView is a UIView.
  2. UserInteractionIsEnabled is true (since I can select the text).
  3. There is only one window, but I am calling self.window.makeKeyAndVisible() at the point where canBecomeFirstResonder is called.
  4. I have implemented canBecomeFirstResponder() (returning True). (It is called right before the gesture recognizer brings up the first menu but not after that.)
  5. I do call self.becomeFirstResponder() (even though it already is).
  6. I have implemented canPerformAction(). This is called a lot both with first-menu and sub-menu items. I return True for the items I want to use.

What else? Thanks!!


Solution

  • I asked Apple for help on this. The fix is to add

    sharedMC.hideMenu()
    

    right before the call to showMenu().

    I think the issue is that my code is not what had presented the Menu originally and so I had to hide it before my code could show it. I note (from notifications) that the menu was not officially "hidden" at all (even though it was no longer visible after pressing my Translate... button).

    I also tried just changing the menuItems and calling update(), but that also didn't work, probably again for the same reason.