Using the UIMenuController and Manipulating the Responder Chain

A detailed guide to handling events through UIMenuController with Swift

This piece focuses on the UIMenuController functionality and how it interacts with the UIResponder chain for handling events. It assumes that you are at least somewhat familiar with both, but if not please go read up a bit before continuing. You can also find an example project of this at the end.

Alright, now let’s get into the main portion of this. When a UIMenuController is displayed, it needs to have a firstResponder object. Usually, you just set the view that you want to display the menu above as the first responder and you’re good to go, but what if you want to show this menu above one view while another view is acting as the first responder? For example when a UITextView/UITextField is active and a user is typing something without having the keyboard dismiss. Well, there just happens to be a way to handle that, but you’ll have to do some manipulation of the responder chain to achieve it. Thankfully Apple has made it relatively straightforward to accomplish.

What we’re going to do is take two separate UIResponder chains, one representing the view the menu is presented on, let’s call that the “bubble view”, and one representing the text view that the user is typing into, and merge them. This will be within the context of a messaging feature, similar to Instagram’s chat. The goal will be to let the users type out messages while simultaneously being able to long-press on a message bubble to perform actions on it.

Here’s an example of our goal:

To start, we need to get a long-press gesture set up and attached, customize the UIMenuController singleton, and override canPerformAction in the presenting view to enable it to display the menu. I’m going to assume that’s ready to go from here on, but there are references at the bottom of this piece if you’d like more details.

We have the menu ready and running, but you might notice an issue. Whenever the menu pops up it causes the keyboard to hide.

To fix this, we’ll tweak the responder chain a bit. Essentially, we need the input text field to be the first responder, but for the view, we are tapping on to be the determiner of where the menu displays and what actions are shown. This is achieved by replacing the text field’s “next responder” object with the view we are tapping on.

First, we subclass UITextField and override nextResponder and provide our own override. This override will be the “bubble view” from above.

weak var nextResponderOverride: UIResponder?
override var next: UIResponder? {
if nextResponderOverride != nil {
return nextResponderOverride
} else {
return super.next
}
}

Along with this we also need to override the canPerformAction method on the UITextField so we can allow the overridden nextResponder to make the determination. This will be how we can allow the text field to “pretend” it can perform the UIMenuController’s actions later on.

override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if nextResponderOverride != nil {
return false
} else {
return super.canPerformAction(action, withSender: sender)
}
}

The gist of what’s going on here is that if we have set our nextResponderOverride, i.e., we want the “bubble view” to handle this, we just return false no matter what. This causes the UIMenuController to check the text view's next responder which we’ve overridden to be our “bubble view”, and thus we’ve completed the responder chain swap.

Now the only thing left to do is set up our “bubble view” to handle the desired actions. We do that with the following:

// Ensure our bubble view can be the first responder
override var canBecomeFirstResponder: Bool {
return true
}
// Set it to be able to perform desired actions
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
// Extend this to check for all actions
if action == #selector(oneMethod) {
return true
} else {
return false
}
}

Now, when you have the keyboard open and trigger your menu to display, it’ll display without taking over and hiding the keyboard! 🎉

Please drop a comment below with any questions or difficulties that come up when implementing this.

There will also be a follow-up post soon which will cover some of the edge case bugs that can show up when building this user interaction, such as the actions getting copied over when long-pressing into the text field. You can see this happen in the example below and within Instagram’s chat feature.

An example project of this in action is available here: https://github.com/alexpersian/MenuItemTester

Stack Overflow reference answer: https://stackoverflow.com/a/23849955/3434244