I am trying to override the default UIMenuController
so that only my custom item "Define..." appears when the user selects text in its text view. I haven't had much luck with the approaches I've found online thus far.
To be more specific, I have subclassed a UIViewController
and used canPerformAction()
to exclude all actions except my define method.
override func becomeFirstResponder() -> Bool {
return true
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
let canPerform: Bool
if action == #selector(defineWord){
canPerform = true
}
else {
canPerform = false
}
print("action = \(action), canPerform = \(canPerform)")
return canPerform
}
In the view controller's viewDidLoad()
, I've included the following:
let shared = UIMenuController.shared
let menuItemDefine = UIMenuItem(title: "Define...", action: #selector(self.defineWord))
shared.menuItems = [menuItemDefine]
Whenever I select text in the view, the console goes through each possible action that might appear in the UIMenuController
and says they can't be performed, with the exception of my custom action:
action = cut:, canPerform = false
action = select:, canPerform = false
(and so on, until...)
action = defineWord, canPerform = true
But the resulting edit menu contains "Copy", "Look Up", "Share", and "Define...". These don't appear in the console, which makes me think that a different approach is called for.
Note that I've also tried subclassing UITextView
and using the above code as appropriate, but the result is the same.
Any ideas where I'm going wrong?
This might help everyone who is asking this question that how to remove "Copy", "Select All" etc.. standard menu items or UIResponderStandardEditActions
that are still visible when you have already returned false
in canPerformAction:
.
It is related to responder chain. As canPerformAction:
is called for every responder, for some of those it may be returning true in canPerformAction:
as a default value.
Thus to check where it is failing I found it by overriding this canPerformAction:
for every element I used in my controller
For example in my view controller I had a webview
and the mistake I was doing was that I was overriding the canPerformAction:
in the delegate methods i.e I was doing something like below
extension viewcontroller: UIWebViewDelegate{
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
But the point is that you have to do it for element and not as the delegate method.
extension UIView {
func dropRoundCorners() {
self.layer.cornerRadius = 10.0;
self.clipsToBounds = true;
}
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
extension UIImageView{
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
extension UIScrollView{
open override func canPerformAction(_ action: Selector, withSender
sender: Any?) -> Bool {
return false
}
}
extension UISlider{
open override func canPerformAction(_ action: Selector, withSender
sender: Any?) -> Bool {
return false
}
}
extension UIWebView{
open override func canPerformAction(_ action: Selector, withSender
sender: Any?) -> Bool {
return false
}
}
I hope this is useful to anyone whose is stuck with this issue.
Following are links that might help you with details:
UIResponder reference
very important read the discussion here regarding responder
some what related
May be it is too late for the answer, but it can be helpful for other users. So, my solution is: I created custom UITextView and redefined the following methods:
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
//Here you can check for all action what you need
return (action == @selector(yourCustomAction)) ? YES : NO;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With