在WKWebView中禁用整个UIMenuController编辑菜单

需求

我有一个WKWebView并希望从编辑菜单中删除系统菜单项(复制,定义,共享…)并显示我自己的。

我的目标是iOS 8和9.我目前正在使用Xcode 7.0.1模拟器(iOS 9)和运行iOS 9.0.2的iPhone 6进行测试。

标准方法不起作用

我知道实现这一目标的标准方法是通过WKWebView并实现-canPerformAction:withSender: . 但是,我发现使用WKWebView -canPerformAction:withSender:没有调用-canPerformAction:withSender: copy:define: actions。 这似乎是一个已知的错误( WKWebView和UIMenuController )。

示例应用程序: https : //github.com/dwieringa/WKWebViewCustomEditMenuBug

 @implementation MyWKWebView - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { NSLog(@"ACTION: %@", NSStringFromSelector(action)); if (action == @selector(delete:)) { // adding Delete as test (works) return YES; } // trying to remove everything else (does NOT work for Copy, Define, Share...) return NO; } - (void)delete:(id)sender { NSLog(@"Delete menu item selected"); } @end 

输出:(注意没有copy:define:动作)

 2015-10-20 12:28:32.864 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: cut: 2015-10-20 12:28:32.865 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: select: 2015-10-20 12:28:32.865 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: selectAll: 2015-10-20 12:28:32.865 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: paste: 2015-10-20 12:28:32.866 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: delete: 2015-10-20 12:28:32.866 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: _promptForReplace: 2015-10-20 12:28:32.866 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: _transliterateChinese: 2015-10-20 12:28:32.867 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: _showTextStyleOptions: 2015-10-20 12:28:32.907 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: _addShortcut: 2015-10-20 12:28:32.908 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: _accessibilitySpeak: 2015-10-20 12:28:32.908 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: _accessibilitySpeakLanguageSelection: 2015-10-20 12:28:32.908 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: _accessibilityPauseSpeaking: 2015-10-20 12:28:32.909 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: makeTextWritingDirectionRightToLeft: 2015-10-20 12:28:32.909 WKWebViewCustomEditMenuBug[45804:21121480] ACTION: makeTextWritingDirectionLeftToRight: 

计划的解决方法

我现在的愿望是完全隐藏编辑菜单,并使用QBPopupMenu将其替换为自定义菜单。

我的问题是我无法找到隐藏或禁用标准编辑菜单的方法。 我发现了一些隐藏它的建议[UIMenuController sharedMenuController].menuVisible = NO;UIMenuControllerWillShowMenuNotification ,但我无法UIMenuControllerWillShowMenuNotification工作。 它与WillShowMenu没有任何影响。 我可以在DidShowMenu隐藏它,但到那时为时已晚,我得到一个菜单闪存。

我还尝试使用[[UIMenuController sharedMenuController] setTargetRect:CGRectMake(0, 0, 1, 1) inView:self.extraView];在可见区域外找到它[[UIMenuController sharedMenuController] setTargetRect:CGRectMake(0, 0, 1, 1) inView:self.extraView]; 但是再次使用WillShowMenu这样做没有任何影响,而且对于DidShowMenu来说已经太晚了。

这里有实验: https : //github.com/dwieringa/WKWebViewEditMenuHidingTest

我错过了什么? 是否有其他方法可以禁用或隐藏WKWebView的标准编辑菜单?

尝试让您的视图控制器成为第一响应者并阻止它退出第一响应者

 - (BOOL)canResignFirstResponder { return NO; } - (BOOL)canBecomeFirstResponder { return YES; } 

https://github.com/dwieringa/WKWebViewEditMenuHidingTest/pull/1

根据您的解决方法,我发现:

 -(void)menuWillShow:(NSNotification *)notification { NSLog(@"MENU WILL SHOW"); dispatch_async(dispatch_get_main_queue(), ^{ [[UIMenuController sharedMenuController] setMenuVisible:NO animated:NO]; }); } 

将阻止菜单闪烁90%的时间..仍然不够好,但它是我们找到一个体面的解决方案之前的另一种解决方法。

经过一番观察,我修好了它。

-canPerformAction:withSender:我为_share_define选项返回NO ,因为我在我的项目中不需要它们。 它在第一次选择单词时按预期工作,但从第二次显示选项。

简单修复:添加[self becomeFirstResponder];tapGuesture或Touch委托方法中

 -(BOOL)canPerformAction:(SEL)action withSender:(id)sender { SEL defineSEL = NSSelectorFromString(@"_define:"); if(action == defineSEL){ return NO; } SEL shareSEL = NSSelectorFromString(@"_share:"); if(action == shareSEL){ return NO; } return YES; } // Tap gesture delegate method - (void)singleTap:(UITapGestureRecognizer *)sender { lastTouchPoint = [sender locationInView:self.webView]; [self becomeFirstResponder]; //added this line to fix the issue// } 

嘿伙计们花了一个小时后,我找到了%100成功率的脏解决方案。

逻辑是; 检测UIMenuController何时显示并更新它。

在你的ViewController(包含WKWebView)中,像这样在viewDidLoad()中添加UIMenuControllerDidShowMenu观察者;

 override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver( self, selector: #selector(uiMenuViewControllerDidShowMenu), name: NSNotification.Name.UIMenuControllerDidShowMenu, object: nil) } 

不要忘记在deinit中删除观察者。

  deinit { NotificationCenter.default.removeObserver( self, name: NSNotification.Name.UIMenuControllerDidShowMenu, object: nil) } 

在您的选择器中,更新UIMenuController,如下所示:

 func uiMenuViewControllerDidShowMenu() { if longPress { let menuController = UIMenuController.shared menuController.setMenuVisible(false, animated: false) menuController.update() //You can only call this and it will still work as expected but i also call setMenuVisible just to make sure. } } 

在您调用UIMenuController的ViewController中,将调用此方法。 我正在开发浏览器应用程序,所以我也有searchBar,用户可能想要将文本粘贴到那里。 因为我在我的webview中检测到longPress并检查UIMenuController是否被WKWebView召唤。

此解决方案的行为与gif相同。 你可以看一秒菜单,但你不能点击它。 你可以尝试在它消失之前点击它,但你不会成功。 请尝试告诉我你的结果。

我希望它对某人有帮助。

干杯。

在此处输入图像描述

此错误实际上是由WKContentView中添加的操作引起的,WKContentView是一个私有类。 您可以添加一个UIView扩展来解决它,如下所示:

 import UIKit extension UIView { open override class func initialize() { guard NSStringFromClass(self) == "WKContentView" else { return } swizzleMethod(#selector(canPerformAction), withSelector: #selector(swizzledCanPerformAction)) } fileprivate class func swizzleMethod(_ selector: Selector, withSelector: Selector) { let originalSelector = class_getInstanceMethod(self, selector) let swizzledSelector = class_getInstanceMethod(self, withSelector) method_exchangeImplementations(originalSelector, swizzledSelector) } @objc fileprivate func swizzledCanPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { return false } } 

这是我的最终解决方案,改编自此处发布的解决方案。 关键是要监听UIMenuControllerWillShowMenu通知,然后再UIMenuControllerWillShowMenu Dispatch.main.async来隐藏菜单。 这似乎可以避免闪烁的菜单。

我的示例使用UITextField ,但它应该很容易适应WKWebView

 class NoMenuTextField: UITextField { override func didMoveToSuperview() { super.didMoveToSuperview() if superview == nil { deregisterForMenuNotifications() } else { registerForMenuNotifications() } } func registerForMenuNotifications() { NotificationCenter.default.addObserver(forName: Notification.Name.UIMenuControllerWillShowMenu, object: nil, queue: OperationQueue.main) { _ in DispatchQueue.main.async { UIMenuController.shared.setMenuVisible(false, animated: false) UIMenuController.shared.update() } } } func deregisterForMenuNotifications() { NotificationCenter.default.removeObserver(self, name: Notification.Name.UIMenuControllerWillShowMenu, object: nil) } } 

我使用的一种方法是使用CSS简单地禁用菜单。 CSS属性名为-webkit-touch-callout: none; 。 您可以将它应用于顶级元素并为整个页面或任何子元素禁用它,并以更高的精度禁用它。 希望有所帮助。

pragma mark – WKNavigationDelegate

 - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation { // Add: // Disable LongPress and Selection, no more UIMenucontroller [self.wkWebView evaluateJavaScript:@"document.documentElement.style.webkitUserSelect='none'" completionHandler:nil]; [self.wkWebView evaluateJavaScript:@"document.documentElement.style.webkitTouchCallout='none'" completionHandler:nil]; }