ios wkwebview uimenucontroller

ios - Deshabilite todo el menú de edición de UIMenuController en WKWebView



(10)

marca de pragma - 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]; }

Requisito

Tengo un WKWebView y me gustaría eliminar los elementos del menú del sistema (Copiar, Definir, Compartir ...) del Menú Editar y presentar el mío.

Estoy apuntando a iOS 8 y 9. Actualmente estoy probando con el simulador Xcode 7.0.1 (iOS 9) y mi iPhone 6 con iOS 9.0.2.

El método estándar no funciona

Sé que la forma estándar de lograr esto es subclasificar WKWebView e implementar -canPerformAction:withSender: Sin embargo, he encontrado que con WKWebView -canPerformAction:withSender: no se llama para la copy: o define: actions. Esto parece ser un error conocido ( WKWebView y UIMenuController ).

Aplicación de ejemplo: 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

Salida: (nota no copy: o define: acción)

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:

Solución planificada

Mi deseo ahora es ocultar completamente el menú de edición y reemplazarlo con un menú personalizado utilizando QBPopupMenu .

Mi problema es que no he podido encontrar una manera de ocultar o deshabilitar el menú de edición estándar. He encontrado algunas sugerencias para ocultarlo con [UIMenuController sharedMenuController].menuVisible = NO; en UIMenuControllerWillShowMenuNotification , pero no he podido hacer que esto funcione. No tiene efecto con WillShowMenu . Puedo ocultarlo en DidShowMenu pero en ese momento ya es demasiado tarde y aparece un menú de flash.

También he intentado ubicarlo fuera del área visible usando [[UIMenuController sharedMenuController] setTargetRect:CGRectMake(0, 0, 1, 1) inView:self.extraView]; , pero hacerlo de nuevo con WillShowMenu no tiene ningún efecto y con DidShowMenu es demasiado tarde.

Experimentos disponibles aquí: https://github.com/dwieringa/WKWebViewEditMenuHidingTest

¿Qué me estoy perdiendo? ¿Hay alguna otra forma de desactivar u ocultar el menú de edición estándar para WKWebView?


Aquí está mi solución final, adaptada de las soluciones publicadas aquí. La clave es escuchar la notificación UIMenuControllerWillShowMenu y luego Dispatch.main.async para ocultar el menú. Esto parece hacer el truco para evitar el menú intermitente.

Mi ejemplo utiliza un UITextField , pero debería adaptarse fácilmente a un 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) } }


Basado en su solución, descubrí que:

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

Evitará que el menú parpadee el 90% de las veces. Aún no es lo suficientemente bueno, pero es otra solución para que encontremos una solución decente.


En iOS 11, he encontrado una solución simple por una extensión de WKWebView. No he comprobado si esto funcionará en versiones anteriores de iOS. El siguiente es un ejemplo simple con un elemento de menú.

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") } }


Este error es realmente causado por las acciones que se agregan en WKContentView, que es una clase privada. Podría agregar una extensión UIView para solucionarlo de esta manera:

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 } }


Hola chicos, después de pasar una hora en eso, encontré una solución sucia con un porcentaje de éxito del 100%.

La lógica es; Detectar cuándo se mostró UIMenuController y actualizarlo.

En su ViewController (que contiene WKWebView), agregue el observador UIMenuControllerDidShowMenu en viewDidLoad () de esta manera;

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

No te olvides de quitar el observador en deinit.

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

Y en su selector, actualice UIMenuController de esta manera:

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. } }

En su ViewController que haya llamado al UIMenuController, este método será llamado. Estoy desarrollando la aplicación del navegador, así que también tengo la barra de búsqueda y el usuario puede querer pegar texto allí. Por eso detecto longPress en mi vista web y verifico si UIMenuController es convocado por WKWebView.

Esta solución se comportará como en gif. Puedes ver el menú por un segundo, pero no puedes tocarlo. Puedes intentar tocarlo antes de que desaparezca, pero no tendrás éxito. Por favor, intenta y dime tus resultados.

Espero que esto ayude a alguien.

Aclamaciones.



Lo arreglé después de alguna observación.

En -canPerformAction:withSender: estoy devolviendo NO a las opciones _share y _define ya que no las necesito en mi proyecto. Funciona como se esperaba en la selección de palabras por primera vez, pero muestra las opciones desde la segunda vez.

[self becomeFirstResponder]; simple: Agrega [self becomeFirstResponder]; en los tapGuesture o Touch delegate

-(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// }


Probé la solución de Stephan Heilner pero no se compiló en Swift 4.

Esta es mi implementación para deshabilitar el controlador de menú en un WKWebView que funciona con Swift 4.

En mi subclase WKWebView, agregué estas propiedades y funciones:

var wkContentView: UIView? { return self.subviewWithClassName("WKContentView") } private func swizzleResponderChainAction() { wkContentView?.swizzlePerformAction() }

Luego, agregué una extensión en el mismo archivo, pero fuera de la subclase 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 } }

Y, finalmente, llamé a la función swizzleResponderChainAction() desde el inicializador (puede anular el inicializador designado o crear uno conveniente):

override init(frame: CGRect, configuration: WKWebViewConfiguration) { super.init(frame: frame, configuration: configuration) swizzleResponderChainAction() }

Ahora, el WKWebView no se bloquea más cuando se usa un controlador UIMenu.


Una forma que he usado es simplemente deshabilitar el menú usando CSS. La propiedad CSS se llama -webkit-touch-callout: none; . Puede aplicarlo al elemento de nivel superior y deshabilitarlo para toda la página o cualquier elemento secundario y deshabilitarlo con más precisión. Espero que ayude.