I'm using legacy webkit based application to generate form on macOS native app (cocoa application written in objective-c)
The following callback to called right before the javascript is loaded to view, and allow current class code (objc) to be used inside the javascript that's about to be loaded.
- (void)webView:(WebView *)webView windowScriptObjectAvailable:(WebScriptObject *)windowScriptObject {
[windowScriptObject setValue:self forKey:@"app"];
}
Unfortunately, it's been deprecated long ago and I'd like use the updated replacement for webView object which is WKWebView
. However, the callback above is delegate method from WebFrameLoadDelegate
which is deprecated as well. Perhaps anybody knows how to inject our native code in javascript using WKWebView ?
thanks
This is how you do it with WKWebView
. YourWebView
is UIView or ViewController class.
@interface YourWebView () <WKNavigationDelegate, WKScriptMessageHandler>
- (void)injectWSKitScriptInUserContentController:(WKUserContentController*)userContentController;
@end
in your implementation in -init or -initWithFrame:
self.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
self.autoresizesSubviews = YES;
self.wantsLayer = YES;
WKWebViewConfiguration* conf = [[WKWebViewConfiguration alloc] init];
conf.suppressesIncrementalRendering = NO;
conf.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeAll;
WKUserContentController* userContentController = [[WKUserContentController alloc] init];
[self injectWSKitScriptInUserContentController:userContentController];
[userContentController addScriptMessageHandler:self name:@"yourscript"];
conf.userContentController = userContentController;
#ifdef DEBUG
NSLog(@"Developer Extras Enabled");
[conf.preferences setValue:@YES forKey:@"developerExtrasEnabled"];
#endif
WKWebView *webView = [[WKWebView alloc] initWithFrame:frame configuration:conf];
webView.navigationDelegate = self;
add webView
to view or where ever you need it and define a method to inject js.
-(void)injectWSKitScriptInUserContentController:(WKUserContentController*)userContentController {
NSBundle* bundle = [NSBundle bundleForClass:[YourWebView class]];
NSString* scriptLocation = [bundle pathForResource:@"yourscript" ofType:@"js"];
NSString* scriptSource = [NSString stringWithContentsOfFile:scriptLocation encoding:NSUTF8StringEncoding error:nil];
WKUserScript* userScript = [[WKUserScript alloc] initWithSource:scriptSource injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
[userContentController addUserScript:userScript];
}
and then follow the protocols on what else to implement in YourWebView in example something like interaction with your webview, like forward, reload, back, etc..
and finally you will want to add a javascript file as starting point to your app. "yourscript.js" mentioned above.
(function() {
"use strict";
var Events = {
listeners: { },
gc: function() {
var events = Object.keys(this.listeners)
for (var i = events.length - 1; i >= 0; i -= 1) {
var eventName = events[i],
listeners = this.listeners[eventName]
if (listeners.length === 0) {
delete this.listeners[eventName]
}
}
},
once: function(name, listener) {
if (name in this.listeners) {
this.listeners[name].push({ oneshot: true, listener: listener })
return
}
this.listeners[name] = [
{ oneshot: true, listener: listener },
]
},
on: function(name, listener) {
if (name in this.listeners) {
this.listeners[name].push({ listener: listener })
return
}
this.listeners[name] = [ { listener: listener } ]
},
off: function(name, listener) {
if ( ! (name in this.listeners)) {
return
}
var listeners = this.listeners[name]
for (var i = listeners.length - 1; i >= 0; i -= 1) {
if (listeners[i].listener === listener) {
listeners.splice(i, 1)
return
}
}
},
trigger: function(name, arg) {
if ( ! (name in this.listeners)) {
return
}
var event = { stopIteration: false, data: arg }
var listeners = this.listeners[name]
for (var i = 0; i < listeners.length; i += 1) {
var listener = listeners[i]
try {
listener.listener(event)
} catch (e) { }
if (listener.oneshot) {
listeners.splice(i, 1)
i -= 1
}
}
this.gc()
},
}
var ETimeout = new Error('WSKit: configuration timeout'),
_config = { resolve: null, reject: null, resolved: false }
window.WSKit = {
configuration: new Promise(function(resolve, reject) {
_config.resolve = resolve
_config.reject = reject
}),
addEventListener: function(name, listener, config) {
config = config || { }
if (config.oneshot) {
Events.once(name, listener)
} else {
Events.on(name, listener)
}
},
removeEventListener: function(name, listener) {
Events.off(name, listener)
},
dispatchEvent: function(name, arg) {
Events.trigger(name, arg)
},
}
setTimeout(function() {
if ( ! _config.resolved) {
_config.reject(ETimeout)
}
}, 5000)
WSKit.addEventListener('configure', function(ev) {
_config.resolve(ev.data)
})
window.webkit.messageHandlers.webscreen.postMessage('obtainconfiguration')
})();
This should pretty much work