Ошибка UIWebView: - [UIWebView cut:]: непризнанный селектор, отправленный в экземпляр

В 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

Обновление 2014/10/15: ошибка, все еще присутствующая в iOS 8.0.2 (12A405)

Ответы

Ответ 1

Это ошибка Apple. Проблема заключается в том, что действие cut: отправляется неправильно в цепочке ответчиков и заканчивается отправкой в ​​экземпляр UIWebView вместо внутреннего UIWebDocumentView, который реализует метод.

Пока Apple не исправляет ошибку, давайте немного повеселимся с временем выполнения 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

Используйте этот подкласс в раскадровке.

Удачи.

Ответ 2

Если вы не возражаете, что нет выносок для вырезания/вставки/etc. в случае, когда 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)
    }
}

Ответ 3

Если кому-то интересно, вот быстрая версия метода Льва Натанса:

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

}

Ответ 4

У меня есть аналогичная проблема, поэтому я пробовал все предоставленные решения здесь, однако эти решения не работают и не могут решить проблему. Кроме этого, любое другое решение? Ценю твою поддержку.