Vraag UIWebView-fout: - [UIWebView-cut:]: niet-herkende selector verzonden naar instantie


In de UIWebView, als een invoerelement dat tekst bevat focus heeft en er op een knop wordt gedrukt waardoor de invoer de focus verliest, en vervolgens dubbel op de invoer tikt om de focus te herstellen en Cut (of Kopiëren of Plakken) te selecteren in de pop-upbalk die verschijnt, veroorzaakt de UIWebView om te crashen met de fout:

-[UIWebView cut:]: unrecognized selector sent to instance 0x10900ca60

Demoproject: https://github.com/guarani/WebViewDoubleTapTestTests.git

Ik denk dat dit een a moet zijn UIWebView bug, geen ideeën?

Voor de volledigheid, hier is de inhoud van mijn webweergave,

<html>
    <head>
    </head>
    <body>
        <br><br>
        <input type="text">
        <input type="button">
    </body>
</html>

Heeft een foutenrapport ingediend bij Apple: 15894403

Update 2014/10/15: bug nog steeds aanwezig in iOS 8.0.2 (12A405)


15
2018-01-23 18:45


oorsprong


antwoorden:


Dit is een Apple-bug. Het probleem is de cut: actie wordt fout verzonden in de responderketen en wordt uiteindelijk verzonden naar de UIWebView bijvoorbeeld in plaats van de interne UIWebDocumentView, die de methode implementeert.

Totdat Apple de bug repareert, laten we wat plezier maken met de Objective C-runtime.

Hier, ik subklasse UIWebView met als doel iedereen te ondersteunen UIResponderStandardEditActions methoden, door ze door te sturen naar de juiste interne instantie.

@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

Gebruik deze subklasse in je storyboard.

Veel plezier.


13
2018-01-23 22:06



Als je het niet erg vindt dat er geen callout is voor knippen / plakken / etc. in het geval dat de UIWebview ten onrechte eerste responder wordt, kunt u deze ook met deze categorie repareren. Dit verbiedt niet knippen / plakken / etc. wanneer de UIWebDocumentView (correct) eerste responder wordt.

@implementation UIWebView (NoWrongPerformWebview)

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
    return NO;
}

@end

// Swift 4-compatibele versie

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
2018-02-24 11:01



Als iemand geïnteresseerd is, hier is de snelle versie van de Leo Natans-methode:

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

}

1
2017-11-23 06:48