21

There apparently used to be an easy way to prevent the "More..." label from appearing in UIMenuController when you added more than one custom menu item. You just had to remove all of the system menu items. There was even a workaround here for still having copy work. You just had to implement a custom copy command using a different selector and then override canPerformAction:withSender: to not show the system copy:

-(BOOL)canPerformAction:(SEL)action withSender:(id)sender 
{
    if (action == @selector(copy:))
       return NO;
    else
       // logic to show or hide other things
}

Unfortunately this method no longer works (at least in a UIWebView subclass). canPerformAction:withSender: is called for every system menu item except copy: so the result is that the system copy menu item is always displayed. This means that if you have more than one custom menu item, they are always hidden behind "More..."

So, is there a way to really remove the system's copy item or some alternate way to prevent menu items from hiding behind "More..."?

Update

This is the output I get when I override canPerformAction:withSender: notice that the method is never called for the "copy:" action:

cannot perform action cut: with sender <UIMenuController: 0x7227d30>.
cannot perform action select: with sender <UIMenuController: 0x7227d30>.
cannot perform action selectAll: with sender <UIMenuController: 0x7227d30>.
cannot perform action paste: with sender <UIMenuController: 0x7227d30>.
cannot perform action delete: with sender <UIMenuController: 0x7227d30>.
cannot perform action promptForReplace: with sender <UIMenuController: 0x7227d30>.
cannot perform action _showMoreItems: with sender <UIMenuController: 0x7227d30>.
cannot perform action _setRtoLTextDirection: with sender <UIMenuController: 0x7227d30>.
cannot perform action _setLtoRTextDirection: with sender <UIMenuController: 0x7227d30>.
can perform action customCopy: with sender <UIMenuController: 0x7227d30>.
can perform action custom1: with sender <UIMenuController: 0x7227d30>.
cannot perform action custom2: with sender <UIMenuController: 0x7227d30>.
can perform action custom3: with sender <UIMenuController: 0x7227d30>.
can perform action custom4: with sender <UIMenuController: 0x7227d30>.
cannot perform action cut: with sender <UIMenuController: 0x7227d30>.
cannot perform action select: with sender <UIMenuController: 0x7227d30>.
cannot perform action selectAll: with sender <UIMenuController: 0x7227d30>.
cannot perform action paste: with sender <UIMenuController: 0x7227d30>.
cannot perform action delete: with sender <UIMenuController: 0x7227d30>.
cannot perform action promptForReplace: with sender <UIMenuController: 0x7227d30>.
cannot perform action _showMoreItems: with sender <UIMenuController: 0x7227d30>.
cannot perform action _setRtoLTextDirection: with sender <UIMenuController: 0x7227d30>.
cannot perform action _setLtoRTextDirection: with sender <UIMenuController: 0x7227d30>.
Community
  • 1
  • 1
lfalin
  • 4,219
  • 5
  • 31
  • 57
  • 1
    Apparently there is no way of removing the copy from UIMenuController within Objective-C, but it’s possible using CSS: `-webkit-user-select:none;` http://www.iphonedevsdk.com/forum/iphone-sdk-development/21715-how-disable-cut-copy-paste-feature.html – David Jul 31 '11 at 14:30
  • Maybe I wasn't clear enough in the opening paragraph, but I'm not trying to prevent user selection. I'm trying to put more than one custom menu item in UIMenuController without their being stuck under the "More..." menu. You used to be able to do this by preventing the Copy command from showing up (http://stackoverflow.com/questions/4311009/uimenucontroller-in-uiwebview-with-custom-menu-items-without-more-menu/4413464#4413464), but that no longer works. – lfalin Aug 01 '11 at 15:22
  • 1
    what do you mean "no longer works"? is it broke in iOS5 or something? – TomSwift Aug 06 '11 at 16:13
  • By "no longer works" I mean that the solution posted here: http://stackoverflow.com/questions/3255070/problems-adding-more-than-one-uimenuitem-to-uimenucontroller doesn't work. It apparently used to. Others (http://stackoverflow.com/questions/4311009/uimenucontroller-in-uiwebview-with-custom-menu-items-without-more-menu/4413464#4413464) have also found that this solution doesn't work. As I mentioned in my update, the overridden method is never called for "copy:". – lfalin Aug 08 '11 at 15:23
  • @Ifalin Did you finally find a solution for newer versions? – Maen Jan 17 '13 at 09:26
  • Also note ... http://stackoverflow.com/questions/19280119/how-to-find-the-responder-that-is-returning-yes-to-canperformactionwithsender – Fattie Jun 14 '14 at 10:12

6 Answers6

13

The technique you linked to still seems to work. I implemented a UIWebView subclass with these methods, and only the A and B items appeared.

+ (void)initialize
{
    UIMenuItem *itemA = [[UIMenuItem alloc] initWithTitle:@"A" action:@selector(a:)];
    UIMenuItem *itemB = [[UIMenuItem alloc] initWithTitle:@"B" action:@selector(b:)];
    [[UIMenuController sharedMenuController] setMenuItems:[NSArray arrayWithObjects:itemA, itemB, nil]];
    [itemA release];
    [itemB release];
}

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
    BOOL can = [super canPerformAction:action withSender:sender];
    if (action == @selector(a:) || action == @selector(b:))
    {
        can = YES;
    }
    if (action == @selector(copy:))
    {
        can = NO;
    }
    NSLog(@"%@ perform action %@ with sender %@.", can ? @"can" : @"cannot", NSStringFromSelector(action), sender);
    return can;
}
lemnar
  • 4,063
  • 1
  • 32
  • 44
  • I'm still using this technique and can confirm it works in iOS 4.3.2. I haven't tried iOS5 beta, however. – TomSwift Aug 07 '11 at 13:44
  • 3
    No, it does **not** work in iOS5 for default selectors like cut, copy, paste, select, selectAll, define, suggest. These are still shown even if I return NO. This neither works in a UITextField nor in a UIWebView. – auco Jan 28 '12 at 21:07
  • 1
    I can confirm it works in my app, built with the latest iOS5 SDK running on iOS4 and iOS5. I hide all of the default copy/paste menu items and show my own - all on a custom UIWebView subclass. – TomSwift Jan 28 '12 at 22:02
  • 1
    Confirmed it too. It works fine with XCode 4.2 and iOS 5.0.1, provided that you implement the custom UIWebView subclass defined here below. – lucasart Feb 10 '12 at 02:14
  • I'm marking this answer as correct since it seems to work for so many people, but I would like to know why it doesn't work for me and others. – lfalin May 22 '12 at 15:43
  • @lfalin, have you added a if control for every default menu items, i.e cut, copy, paste, select? Above code snippet has only copy: selector, so I think it will only remove it not the other ones. – Özgür Jun 19 '12 at 10:40
  • @Comptrol copy: is the only one I'm trying to remove. The problem is that canPerformAction:withSender: is never called for the action copy. – lfalin Jun 19 '12 at 14:54
  • lfalin, you are right. The answer is totally incorrect. How to get rid Copy and etc. menus? – Dmitry Nov 07 '12 at 19:38
  • In UIWebView documentation, they mentioned that, "UIWebview shouldn't be sub classes". Is it legal to subclass it? – Satyam Aug 19 '13 at 05:21
7

for ios >= 5.1 canPerformAction:(SEL)action withSender:(id)sender is not working anymore.

If you are ok with just disable paste action here is a method:

add UITextFieldDelegate to you view controller and implement method like this

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
if(textField == txtEmailRe)
    return ((string.length) > 1 ? NO : YES);
}

it means that if user enter more than one character for each action (it means that probably user is pasting something.) do not accept it in textfield.

it is a good practice for force user enter textfields like e-mail and

Canberk Ersoy
  • 428
  • 1
  • 5
  • 13
  • Best solution for this, hands down! – Jens Bergvall Nov 15 '12 at 11:43
  • 2
    canPerformAction does work in IOS > 5.1. The problem is your dealing with the canPerformAction in the delegate class. The canPerformAction is called on the UITextField itself, not on the delegate. In order canPerformAction to work you should override canPerformAction in a UITextField subclass. – gmauri21 Jun 19 '13 at 19:24
3

lemnar's answer is correct. Implementing a subclass of UIWebView works just fine. This example is OK for a UITextView. For a UIWebView, create a custom subclass as follows:

//
//  MyUIWebView.h
//

#import <UIKit/UIKit.h>

@interface MyUIWebView : UIWebView

@end

And:

//
//  MyUIWebView.m
//

#import "MyUIWebView.h"

@implementation MyUIWebView

-(BOOL)canPerformAction:(SEL)action withSender:(id)sender 
{
    if (action == @selector(copy:))
        return NO;
    else
        // logic to show or hide other things
}

@end

Then, instead of instantiating UIWebView, use MyUIWebView.

UPDATE:

If wanting to disable "copy" but leave "define" (and "translate",) which can be useful, this is how to do it; replace canPerformAction:withSender above with this:

-(BOOL)canPerformAction:(SEL)action withSender:(id)sender 
{
    if (action == @selector(defineSelection:))
    {
        return YES;
    }
    else if (action == @selector(translateSelection:))
    {
        return YES; 
    }
    else if (action == @selector(copy:))
    {
        return NO;
    }

    return [super canPerformAction:action withSender:sender];
}
Community
  • 1
  • 1
lucasart
  • 1,643
  • 1
  • 20
  • 29
  • 1
    no, this does **not** work in iOS 5.0.1. Just tried, still no copy: selector in the canPerformAction: method. – auco Jan 28 '12 at 21:18
  • This isn't supposed to enable "copy" in the menu, but disable it. It works perfectly fine on iOS 5.0.1, provided that you follow the instructions given above. – lucasart Feb 01 '12 at 10:40
  • I wrote "still no 'copy:-selector' in the canPerformAction: method". That means, that the copy action is never sent to this delegate method, so the canPerformAction-method will never be able to return NO. That means, copy cannot be disabled. Again: it does not work with iOS 5.x. I tested it multiple times on multiple machines. The only way it works for you, is when you have an older Xcode/SDK version, as it used to work before. Edit: or you're maybe not using a UIWebView. This question is about UIWebview. The only solution working is outlined below (UIWebBrowserView category) – auco Feb 09 '12 at 11:43
  • auco, I'm not sure why you can't get it to work. As said above by @TomSwift and by myself (See post above with custom subclass MyUIWebView), it works perfectly on iOS 5.0.1 with XCode 4.2. The class in question is UIWebView, see exemple above. I'd like to add a screenshot here, but it'd pollute the post and add unnecessary verbiage. – lucasart Feb 10 '12 at 02:17
3

Here is a solution for iOS5.x that works for me. It's by Josh Garnham, suggesting creating a UIWebBrowserView Category to catch the copy:, paste:, define: selectors.

http://ios-blog.co.uk/iphone-development-tutorials/rich-text-editing-highlighting-and-uimenucontroller-part-3/

@implementation UIWebBrowserView (UIWebBrowserView_Additions)
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
    return NO;
}
@end

Note just FTR: There's a slight typo on that excellent web page. Here's precisely how you do it. Apple will 100% reject this. Make a category

enter image description here

(You have to type in "UIWebBrowserView" since Xcode won't bring up private classes.) Full text of the .h and .m files:

// .h file...
#import "UIWebBrowserView+Tricky.h"
@interface UIWebBrowserView : UIView
@end
@interface UIWebBrowserView(Tricky)
@end

// .m file...
#import "UIWebBrowserView+Tricky.h"
@implementation UIWebBrowserView (Tricky)
-(BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
NSLog(@"don't let Apple see this");
return NO;
}
@end

For the record, a "single click" will still bring up the annoying spellchecker suggestions! But otherwise it does remove the double-click-context-menu totally, it is 100% rejected by Apple.

Fattie
  • 27,874
  • 70
  • 431
  • 719
auco
  • 9,329
  • 4
  • 47
  • 54
  • 2
    unfortunately this seems to be a private method and does not pass the validation – auco Mar 29 '12 at 14:35
  • With some articulation, it is possible to reach the UIWebBrowserView without actually doing anything that is a no-no for AppStore. Objective C is a very dynamic language. Hint: dynamic subclassing and isa swizzling. – Léo Natan Feb 19 '13 at 23:29
  • @JoeBlow You shouldn't have! – Léo Natan Jun 14 '14 at 18:11
1

I'm sorry for my English. But there is an idea.

I think the method canPerformAction were called for many times but you just deal with it once. In this case ,I think there may be another UI Control has called it. For example, the UITextView control in your UIWebView.

I guess you may generate the UI by storyboard. Not every control in storyboard has its own class. You can define a class for the response control and rewrite its canPerformAction method.

sandycs
  • 11
  • 2
  • You are correct, canPerformAction is called by UIWebBrowserView, a subview of UIWebView which returns YES for copy. Hence it is not calling the canPerformAction that was written by us. – Shayan RC Aug 12 '14 at 13:15
0

You could draw your own menu instead of using UIMenuController. That way, you can have as many items as you want displayed at the same time without using Other.

LaC
  • 12,624
  • 5
  • 39
  • 38