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