UIWebView Bug: – :无法识别的select器发送到实例
在UIWebView
,如果包含文本的input元素具有焦点,并按下导致input失去焦点的button,则随后双击input以重新获得焦点并从popup栏中select剪切(或复制或粘贴)看起来会导致UIWebView
崩溃的错误:
-[UIWebView cut:]: unrecognized selector sent to instance 0x10900ca60
演示项目: https : //github.com/guarani/WebViewDoubleTapTestTests.git
我认为这一定是一个UIWebView
错误,有什么想法?
为了完整,这里是我的networking视图的内容,
<html> <head> </head> <body> <br><br> <input type="text"> <input type="button"> </body> </html>
在苹果提交了一个错误报告:15894403
更新2014/10/15:iOS 8.0.2中仍存在问题(12A405)
这是一个苹果的错误。 问题在于cut:
action在响应者链中被错误地发送,最终被发送到UIWebView
实例,而不是实现该方法的内部UIWebDocumentView
。
在Apple修复这个bug之前,让我们来看一下Objective C运行时的一些乐趣。
在这里,我将UIWebView
的子类UIWebView
为支持所有的UIResponderStandardEditActions
方法,通过转发它们到正确的内部实例。
@import ObjectiveC; @interface CutCopyPasteFixedWebView : UIWebView @end @implementation CutCopyPasteFixedWebView - (UIView*)_internalView { UIView* internalView = objc_getAssociatedObject(self, "__internal_view_key"); if(internalView == nil && self.subviews.count > 0) { for (UIView* view in self.scrollView.subviews) { if([view.class.description hasPrefix:@"UIWeb"]) { internalView = view; objc_setAssociatedObject(self, "__internal_view_key", view, OBJC_ASSOCIATION_ASSIGN); break; } } } return internalView; } void webView_implement_UIResponderStandardEditActions(id self, SEL selector, id param) { void (*method)(id, SEL, id) = (void(*)(id, SEL, id))[[self _internalView] methodForSelector:selector]; //Call internal implementation. method([self _internalView], selector, param); } - (void)_prepareForNoCrashes { NSArray* selectors = @[@"cut:", @"copy:", @"paste:", @"select:", @"selectAll:", @"delete:", @"makeTextWritingDirectionLeftToRight:", @"makeTextWritingDirectionRightToLeft:", @"toggleBoldface:", @"toggleItalics:", @"toggleUnderline:", @"increaseSize:", @"decreaseSize:"]; for (NSString* selName in selectors) { SEL selector = NSSelectorFromString(selName); //This is safe, the method will fail if there is already an implementation. class_addMethod(self.class, selector, (IMP)webView_implement_UIResponderStandardEditActions, ""); } } - (void)awakeFromNib { [self _prepareForNoCrashes]; [super awakeFromNib]; } @end
在故事板中使用这个子类。
玩的开心。
如果你不介意没有剪切/粘贴/等的标注。 在这种情况下,当UIWebview错误地成为第一响应者,那么你也可以用这个类别修复它。 这并不禁止剪切/粘贴/等。 当UIWebDocumentView(正确)成为第一响应者。
@implementation UIWebView (NoWrongPerformWebview) - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { return NO; } @end
// Swift 4兼容版本
import UIKit extension UIWebView { override open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { // Should be respond to a certain Selector ?? return responds(to: action) } }
如果有人感兴趣,下面是Leo Natans方法的快速版本:
import Foundation import ObjectiveC var AssociatedObjectHandle: UInt8 = 0 class CustomWebView: UIWebView { func _internalView() -> UIView? { var internalView:UIView? = objc_getAssociatedObject(self, "__internal_view_key") as? UIView if internalView == nil && self.subviews.count > 0 { for view: UIView in self.scrollView.subviews { if view.self.description.hasPrefix("UIWeb") { internalView = view objc_setAssociatedObject(self, "__internal_view_key", view, objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN) } } } return internalView } override func awakeFromNib() { super.awakeFromNib() self._prepareForNoCrashes() } func _prepareForNoCrashes() { let selectors = ["cut:", "copy:", "paste:", "select:", "selectAll:", "delete:", "makeTextWritingDirectionLeftToRight:", "makeTextWritingDirectionRightToLeft:", "toggleBoldface:", "toggleItalics:", "toggleUnderline:", "increaseSize:", "decreaseSize:"] for selName: String in selectors { let selector = NSSelectorFromString(selName) //This is safe, the method will fail if there is already an implementation. let swizzledMethod:IMP = class_getInstanceMethod(CustomWebView.self, #selector(CustomWebView.webView_implement_UIResponderStandardEditActions)) class_addMethod(CustomWebView.self, selector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)) } } func webView_implement_UIResponderStandardEditActions(this:AnyObject, selector:Selector, param:AnyObject) { let method = {(val1: UIView?, val2: Selector, val3: AnyObject) -> Void in self._internalView()?.methodForSelector(selector) } method(self._internalView(), selector, param); } }