Manipulate paste content in WKWebView

后端 未结 1 1470
南笙
南笙 2021-01-26 07:25

I need to manipulate the text that is pasted into a WKWebView (from any source) running an asynchronous operation that can take some time.

My original idea was to use Ja

1条回答
  •  粉色の甜心
    2021-01-26 07:42

    FOR POSTERITY:

    I'm sure there a way to do this using JS exists and is cleaner but I managed to achieve the result using method swizzling:

    //Method Swizzling
    UIView *webContentView = self.webView.contentView;
    if(webContentView != nil)
    {
        //Paste:
        NSError *error;
        [webContentView swizzleMethod:@selector(paste:) withSelector:@selector(my_paste:) error:&error];
        if(error != nil)
        {
            NSLog("Failed to swizzle 'paste:' into WKContentView: %@, error);
            NSAssert(false, error);
        }
    
        ...
    }
    

    Where the contentView is:

    - (UIView *)contentView
    {
        return [self subviewWithClassName:@"WKContentView"];
    }
    

    The method my_paste: need to be part of UIResponder (that is implemented by the private WKContentView)

    #pragma mark - Method Swizzling UIResponder
    
    @interface UIResponder (WebComposerSwizzling)
    
    - (void)my_paste:(id)sender;
    #define original_paste my_paste
    
    @end
    
    @implementation UIResponder (WebComposerSwizzling)
    
    - (void)my_paste:(id)sender
    {
        MailComposerViewController* strongComposer = sCurrentComposer;
        if (strongComposer)
            [strongComposer manipulatePasteboard:nil];
        [self original_paste:sender];
    }
    
    @end
    

    Note that sCurrentComposer is a static variable in my ViewController

    __weak MailComposerViewController* sCurrentComposer;
    

    The various utilities:

    UIView+SubviewSearch

    import UIKit
    
    extension UIView {
    
        /// Find a subview corresponding to the className parameter, recursively.
        @objc public func subviewWithClassName(_ className: String) -> UIView? {
            if NSStringFromClass(type(of: self)) == className {
                return self
            } else {
                for subview in subviews {
                    return subview.subviewWithClassName(className)
                }
            }
            return nil
        }
    }
    

    NSObject+Swizzling

    import Foundation
    
    extension NSObject {
    
        enum NSObjectSwizzlingError: Error {
            case originalSelectorNotFound
        }
    
        @objc public func swizzleMethod(_ currentSelector: Selector, withSelector newSelector: Selector) throws {
            if let currentMethod = self.instanceMethod(for: currentSelector),
                let newMethod = self.instanceMethod(for:newSelector) {
                method_exchangeImplementations(currentMethod, newMethod)
            } else {
                throw NSObjectSwizzlingError.originalSelectorNotFound
            }
        }
    
        @objc public func instanceMethod(for selector: Selector) -> Method? {
            let classType: AnyClass! = object_getClass(self)
            return class_getInstanceMethod(classType, selector)
        }
    }
    

    (I'm sorry about the Swift <> OBJ-C mix)

    0 讨论(0)
提交回复
热议问题