我有一个WKWebView
,想要从编辑菜单中删除系统菜单项(复制,定义,共享...)并显示我自己的项目。
我的目标是iOS 8和9.我目前正在使用Xcode 7.0.1模拟器(iOS 9)和运行iOS 9.0.2的iPhone 6进行测试。
我知道实现这一目标的标准方法是通过继承WKWebView
并实施
-canPerformAction:withSender:
。但是,我发现WKWebView
-canPerformAction:withSender:
并未调用copy:
或define:
行为。这似乎是一个已知错误(WKWebView and 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
隐藏它的一些建议,但我无法让它工作。它对WillShowMenu
没有影响。我可以在DidShowMenu
隐藏它,但到那时为时已晚,我得到一个菜单闪烁。
我还尝试使用[[UIMenuController sharedMenuController] setTargetRect:CGRectMake(0, 0, 1, 1) inView:self.extraView];
在可见区域之外找到它,但是再次使用WillShowMenu
这样做没有任何影响,而使用DidShowMenu
则为时已晚。
此处提供的实验:https://github.com/dwieringa/WKWebViewEditMenuHidingTest
我错过了什么?是否有其他方法可以禁用或隐藏WKWebView的标准编辑菜单?
答案 0 :(得分:6)
尝试让您的视图控制器成为第一响应者并阻止其退出第一响应者
- (BOOL)canResignFirstResponder {
return NO;
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
https://github.com/dwieringa/WKWebViewEditMenuHidingTest/pull/1
答案 1 :(得分:5)
根据您的解决方法,我发现:
rem Saved in D:\Temp\WriteText.bat
@echo off
setlocal enabledelayedexpansion
ren test.txt in.tmp
set /p line1=<in.tmp
>test.txt echo %line1%
>>test.txt Echo Test4
for /f "skip=1 delims=" %%a in (in.tmp) do >>test.txt Echo %%a
del in.tmp
将阻止菜单在90%的时间内闪烁..仍然不够好,但在找到合适的解决方案之前,这是另一种解决方法。
答案 2 :(得分:2)
嘿伙计们花了一个小时后,我找到了%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相同。你可以看一秒菜单,但你不能点击它。你可以尝试在它消失之前点击它,但你不会成功。请尝试告诉我你的结果。
我希望它有所帮助。
干杯。
答案 3 :(得分:2)
此错误实际上是由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
}
}
答案 4 :(得分:2)
我尝试了Stephan Heilner的解决方案,但未在Swift 4中编译。
这是我的实现,用于在与Swift 4兼容的WKWebView中禁用menuController。
在我的WKWebView子类中,添加了以下属性和功能:
var wkContentView: UIView? {
return self.subviewWithClassName("WKContentView")
}
private func swizzleResponderChainAction() {
wkContentView?.swizzlePerformAction()
}
然后,我在同一文件中添加了一个扩展名,但不在WKWebView子类中:
// MARK: - Extension used for the swizzling part linked to wkContentView (see above)
extension UIView {
/// Find a subview corresponding to the className parameter, recursively.
func subviewWithClassName(_ className: String) -> UIView? {
if NSStringFromClass(type(of: self)) == className {
return self
} else {
for subview in subviews {
return subview.subviewWithClassName(className)
}
}
return nil
}
func swizzlePerformAction() {
swizzleMethod(#selector(canPerformAction), withSelector: #selector(swizzledCanPerformAction))
}
private func swizzleMethod(_ currentSelector: Selector, withSelector newSelector: Selector) {
if let currentMethod = self.instanceMethod(for: currentSelector),
let newMethod = self.instanceMethod(for:newSelector) {
let newImplementation = method_getImplementation(newMethod)
method_setImplementation(currentMethod, newImplementation)
} else {
print("Could not find originalSelector")
}
}
private func instanceMethod(for selector: Selector) -> Method? {
let classType = type(of: self)
return class_getInstanceMethod(classType, selector)
}
@objc private func swizzledCanPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
最后,我从初始化程序中调用了swizzleResponderChainAction()
函数(您可以覆盖指定的初始化程序,也可以创建一个方便的初始化程序):
override init(frame: CGRect, configuration: WKWebViewConfiguration) {
super.init(frame: frame, configuration: configuration)
swizzleResponderChainAction()
}
现在,使用UIMenuController时,WKWebView不再崩溃。
答案 5 :(得分:1)
经过一番观察,我修好了它。
在-canPerformAction:withSender:
我正在为NO
和_share
选项返回_define
,因为我在项目中并不需要它们。它在第一次选择单词时按预期工作,但从第二次显示选项。
简单修复:在[self becomeFirstResponder];
或触摸委托方法
tapGuesture
-(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//
}
答案 6 :(得分:1)
这是我的最终解决方案,改编自此处发布的解决方案。关键是要听取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)
}
}
答案 7 :(得分:0)
我使用的一种方法是简单地使用CSS禁用菜单。 CSS属性称为-webkit-touch-callout: none;
。您可以将它应用于顶级元素并为整个页面或任何子元素禁用它,并以更高的精度禁用它。希望有所帮助。
答案 8 :(得分:0)
- (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]; }
答案 9 :(得分:0)
在iOS 11中,我通过WKWebView的扩展找到了一个简单的解决方案。我还没有检查这是否可以在iOS的早期版本中使用。以下是一个菜单项的简单示例。
import UIKit
import WebKit
extension WKWebView {
override open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
switch action {
case #selector(highlightHandler):
return true
default:
return false
}
}
func createEditMenu() { // Should be called once
let highlight = UIMenuItem(title: "Highlight", action: #selector(highlightHandler))
menuItems.append(highlight)
UIMenuController.shared.menuItems = [highlight]
}
@objc func highlightHandler(sender: UIMenuItem) {
print("highlight clicked")
}
}
答案 10 :(得分:0)
子类WKWebView并重写canPerformAction以返回false:
class WebView : WKWebView {
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
答案 11 :(得分:0)
Swift 5.2
NotificationCenter.default.addObserver(self, selector: #selector(willShowMenu(_:)), name: UIMenuController.willShowMenuNotification, object: nil)
...
@objc private func willShowMenu(_ notification: NSNotification) {
DispatchQueue.main.async {
UIView.performWithoutAnimation {
UIMenuController.shared.hideMenu()
}
}
}