2

I have a UIWebView with a contentEditable div in order to implement some kind of rich text editor. I need to trimm the copy & cut options in the UIMenuController that appears in the web view once the user selects any piece of text.

There seems to be a lot of solutions around the web, but for some reason, non of them applies for my scenario.

I've subclassed the UIWebView and implemented the canPerformAction:(SEL)action withSender: to remove the copy and cut, but once the user chooses "Select" or "Select All", a new menu appears, and apparently, the web view does not intercept this action and the canPerform method is not being called.

enter image description here

Is there a way to trimm actions for this cases?

Jeromy French
  • 11,812
  • 19
  • 76
  • 129
Omer
  • 5,470
  • 8
  • 39
  • 64

1 Answers1

0

I will adapt another answer of mine for your case.

The canPerformAction: is actually called on the internal UIWebDocumentView instead of the UIWebView, which you cannot normally subclass. With some runtime magic, it's possible.

We create a class which has one method:

@interface _SwizzleHelper : UIView @end

@implementation _SwizzleHelper

-(BOOL)canPerformAction:(SEL)action
{
    //Your logic here
    return NO;
}

@end

Once you have a web view which you want to control the actions of, you iterate its scroll view's subviews and take the UIWebDocumentView class. We then dynamically make the superclass of the class we created above to be the subview's class (UIWebDocumentView - but we cannot say that upfront because this is private API), and replace the subview's class to our class.

#import "objc/runtime.h"    

-(void)__subclassDocumentView
{
    UIView* subview;

    for (UIView* view in self.scrollView.subviews) {
        if([[view.class description] hasPrefix:@"UIWeb"])
            subview = view;
    }

    if(subview == nil) return; //Should not stop here

    NSString* name = [NSString stringWithFormat:@"%@_SwizzleHelper", subview.class.superclass];
    Class newClass = NSClassFromString(name);

    if(newClass == nil)
    {
        newClass = objc_allocateClassPair(subview.class, [name cStringUsingEncoding:NSASCIIStringEncoding], 0);
        if(!newClass) return;

        Method method = class_getInstanceMethod([_SwizzleHelper class], @selector(canPerformAction:));
        class_addMethod(newClass, @selector(canPerformAction:), method_getImplementation(method), method_getTypeEncoding(method));

        objc_registerClassPair(newClass);
    }

    object_setClass(subview, newClass);
}
Community
  • 1
  • 1
Léo Natan
  • 56,823
  • 9
  • 150
  • 195
  • Interesting approach. I'm wondering why you dynamically create a new subclass of `UIWebDocumentView`. Wouldn't it be easier to just swizzle `-canPerformAction:` of `UIWebDocumentView` directly? – Ortwin Gentz Jan 16 '15 at 21:48
  • It's possible, but this is easier to actually implement. Easier to call super implementation, easier to override the method regardless of which class implements it in the superclass chain. If you were to swizzle the method directly, you'd need to find out which superclass is the first to implement it, and swizzle that - can be problematic if the class is used elsewhere. – Léo Natan Jan 16 '15 at 22:03
  • Can you share Demo for this. It's still not working in my code. – Sonu Jan 17 '15 at 05:41