在UIWebView
中,如果包含文本的输入元素具有焦点,并且按下按钮会导致输入失去焦点,则随后双击输入以重新获得焦点并选择剪切(或复制)显示的弹出栏中的“粘贴”或“粘贴”会导致UIWebView
因错误而崩溃:
-[UIWebView cut:]: unrecognized selector sent to instance 0x10900ca60
演示项目:https://github.com/guarani/WebViewDoubleTapTestTests.git
我认为这必须是UIWebView
错误,任何想法?
为了完整性,以下是我的网页视图的内容
<html>
<head>
</head>
<body>
<br><br>
<input type="text">
<input type="button">
</body>
</html>
在Apple提交了一份错误报告:15894403
更新2019/05/30:iOS 12.0(16E226)中仍然存在错误
答案 0 :(得分:14)
这是一个Apple bug。问题是响应者链中的cut:
操作发送错误,最终被发送到UIWebView
实例而不是实现该方法的内部UIWebDocumentView
。
在Apple修复bug之前,让我们对Objective C运行时有一些乐趣。
在这里,我将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
在故事板中使用此子类。
玩得开心。
答案 1 :(得分:4)
如果您不介意没有剪切/粘贴等标注。在这种情况下,当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)
}
}
答案 2 :(得分:1)
如果有人有兴趣,这里是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);
}
}
答案 3 :(得分:0)
- (UIView *)_internalView {
UIView *internalView = nil;
if (internalView == nil && self.subviews.count > 0) {
for (UIView *view in self.scrollView.subviews) {
if([view.class.description hasPrefix:@"UIWeb"]) {
internalView = view;
break;
}
}
}
return internalView;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
struct objc_method_description methodDescription = protocol_getMethodDescription(@protocol(UIResponderStandardEditActions), aSelector, NO, YES);
if (methodDescription.name == aSelector) {
UIView *view = [self _internalView];
if ([view respondsToSelector:aSelector]) {
return view;
}
}
return [super forwardingTargetForSelector:aSelector];
}