Reputation: 4962
I cannot seem to remove Bold, Italic, and Underline options from the UIWebView. Why is this not possible?
CustomWebView.h:
#import <UIKit/UIKit.h>
@interface CustomUIWebView : UIWebView
@end
CustomWebView.m:
#import <Foundation/Foundation.h>
#import "CustomUIWebView.h"
@implementation CustomUIWebView
-(BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
return NO;
return [super canPerformAction:action withSender:sender];
}
@end
Upvotes: 8
Views: 628
Reputation: 4391
I've investigated a lot to solve this problem and finally found a solution. I first though overriding - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
in the UIViewController
containing the UIWebView
would be sufficient to prevent text style options to show up. But unfortunately, it wasn't as easy as it seems.
The main reason for this is that we have to override the canPerformAction
of the main first responder. Calling [[[UIApplication sharedApplication] keyWindow] performSelector:@selector(firstResponder)]
from the controller informs us that UIWebBrowserView
is the actual main first responder. We would want to subclass UIWebBrowserView
, but since it's a private class, we might have our app rejected by Apple during review process. This answer from @Shayan RC suggested to perform a method swizzling to allow kind of overriding this method without subclassing UIWebBrowserView
(and thus prevent from App Store rejection).
The idea is to add a new method that would replace canPerformAction
. I've created an array with all methods signatures that we want to keep in the Menu. To remove styling options, we just have to not add @"_showTextStyleOptions:"
to this array. Add every other method signature that you want to show (I've added a NSLog
of the signatures, so that you can pick the ones you want).
- (BOOL) mightPerformAction:(SEL)action withSender:(id)sender {
NSLog(@"action : %@", NSStringFromSelector(action));
NSArray<NSString*> *selectorsToKeep = @[@"cut:", @"copy:", @"select:", @"selectAll:", @"_lookup:"]; //add in this array every action you want to keep
if ([selectorsToKeep containsObject:NSStringFromSelector(action)]) {
return YES;
}
return NO;
}
Now we can perform method swizzling to call previous method instead of canPerformAction
, with following method (from the answer of @Shayan RC). This will require to add #import <objc/runtime.h>
.
- (void) replaceUIWebBrowserView: (UIView *)view {
//Iterate through subviews recursively looking for UIWebBrowserView
for (UIView *sub in view.subviews) {
[self replaceUIWebBrowserView:sub];
if ([NSStringFromClass([sub class]) isEqualToString:@"UIWebBrowserView"]) {
Class class = sub.class;
SEL originalSelector = @selector(canPerformAction:withSender:);
SEL swizzledSelector = @selector(mightPerformAction:withSender:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(self.class, swizzledSelector);
//add the method mightPerformAction:withSender: to UIWebBrowserView
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
//replace canPerformAction:withSender: with mightPerformAction:withSender:
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
}
}
Finally, call previous method in viewDidLoad
like so : [self replaceUIWebBrowserView:_webView]
.
It could seems hard with method swizzling, but it allows you to keep the code in your view controller. Please tell me if you encounter any difficulties in implementing previous code.
NOTE : This behavior is much more easy to implement with WKWebView
than with UIWebView
, and UIWebView
is deprecated, you should really consider switching to WKWebView
.
Upvotes: 2