问题
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 Javascript and the WKWebView configuration in order to get the onpaste
event:
WKUserContentController *wkUController = [[WKUserContentController alloc] init];
NSString *pasteJSSource = @"document.addEventListener('onpaste', function(){ window.webkit.messageHandlers.ComposerListener.postMessage('onpaste happened!'); })";
WKUserScript *pasteScript = [[WKUserScript alloc] initWithSource:pasteJSSource injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly: NO];
[wkUController addScriptMessageHandler:self name:@"ComposerListener"];
[wkUController addUserScript:pasteScript];
webViewConfiguration.userContentController = wkUController;
Then my class implements WKScriptMessageHandler
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
NSLog(@"message: %@", message.body);
}
(Ignore Obj-c, swift is ok too)
But I have two problems:
userContentController:didReceiveScriptMessage:
is never called- I don't know how to intercept the pasted code and replace it with something different
Any idea on how to solve this (even without JS that I don't obviously know :P )? Thanks.
回答1:
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)
来源:https://stackoverflow.com/questions/61124006/manipulate-paste-content-in-wkwebview