À l'aide d'une WKWebView dans iOS 8, comment puis-je exécuter une fonction JavaScript du côté natif ou communiquer du JavaScript du côté natif? Il ne semble pas exister de méthode apparentée à stringByEvaluatingJavaScriptFromString:
De UIWebView.
(Je peux utiliser - addScriptMessageHandler:name:
Sur l'objet configuration.userContentController
Pour permettre la communication de JS vers natif, mais je cherche la direction opposée.)
(J'ai déposé un radar pour cela peu après avoir posé la question ici.)
Une nouvelle méthode a été ajoutée il y a quelques jours (merci jcesarmobile pour l'avoir signalée):
Ajouter
-[WKWebView evaluateJavaScript:completionHandler:]
http://trac.webkit.org/changeset/169765
La méthode est disponible dans iOS 8 bêta 3 et plus. Voici la nouvelle signature de méthode:
/* @abstract Evaluates the given JavaScript string.
@param javaScriptString The JavaScript string to evaluate.
@param completionHandler A block to invoke when script evaluation completes
or fails.
@discussion The completionHandler is passed the result of the script evaluation
or an error.
*/
- (void)evaluateJavaScript:(NSString *)javaScriptString
completionHandler:(void (^)(id, NSError *))completionHandler;
Les documents sont disponibles ici: https://developer.Apple.com/documentation/webkit/wkwebview/1415017-evaluatejavascript .
Le script est inséré dans une page qui sera affichée dans WKWebView. Ce script renverra l'URL de la page (mais vous pouvez écrire un autre code JavaScript). Cela signifie que l'événement de script est généré sur la page Web, mais qu'il sera traité dans notre fonction:
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {...}
extension WKUserScript {
enum Defined: String {
case getUrlAtDocumentStartScript = "GetUrlAtDocumentStart"
case getUrlAtDocumentEndScript = "GetUrlAtDocumentEnd"
var name: String { return rawValue }
private var injectionTime: WKUserScriptInjectionTime {
switch self {
case .getUrlAtDocumentStartScript: return .atDocumentStart
case .getUrlAtDocumentEndScript: return .atDocumentEnd
}
}
private var forMainFrameOnly: Bool {
switch self {
case .getUrlAtDocumentStartScript: return false
case .getUrlAtDocumentEndScript: return false
}
}
private var source: String {
switch self {
case .getUrlAtDocumentEndScript, .getUrlAtDocumentStartScript:
return "webkit.messageHandlers.\(name).postMessage(document.URL)"
}
}
fileprivate func create() -> WKUserScript {
return WKUserScript(source: source,
injectionTime: injectionTime,
forMainFrameOnly: forMainFrameOnly)
}
}
}
extension WKWebViewConfiguration {
func add(script: WKUserScript.Defined, scriptMessageHandler: WKScriptMessageHandler) {
userContentController.addUserScript(script.create())
userContentController.add(scriptMessageHandler, name: script.name)
}
}
Init WKWebView
let config = WKWebViewConfiguration()
config.add(script: .getUrlAtDocumentStartScript, scriptMessageHandler: self)
config.add(script: .getUrlAtDocumentEndScript, scriptMessageHandler: self)
webView = WKWebView(frame: UIScreen.main.bounds, configuration: config)
webView.navigationDelegate = self
view.addSubview(webView)
Événements de capture
extension ViewController: WKScriptMessageHandler {
func userContentController (_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if let script = WKUserScript.Defined(rawValue: message.name),
let url = message.webView?.url {
switch script {
case .getUrlAtDocumentStartScript: print("start: \(url)")
case .getUrlAtDocumentEndScript: print("end: \(url)")
}
}
}
}
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
private var webView = WKWebView()
override func viewDidLoad() {
super.viewDidLoad()
let config = WKWebViewConfiguration()
config.add(script: .getUrlAtDocumentStartScript, scriptMessageHandler: self)
config.add(script: .getUrlAtDocumentEndScript, scriptMessageHandler: self)
webView = WKWebView(frame: UIScreen.main.bounds, configuration: config)
webView.navigationDelegate = self
view.addSubview(webView)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
webView.load(urlString: "http://Apple.com")
}
}
extension ViewController: WKScriptMessageHandler {
func userContentController (_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if let script = WKUserScript.Defined(rawValue: message.name),
let url = message.webView?.url {
switch script {
case .getUrlAtDocumentStartScript: print("start: \(url)")
case .getUrlAtDocumentEndScript: print("end: \(url)")
}
}
}
}
extension WKWebView {
func load(urlString: String) {
if let url = URL(string: urlString) {
load(URLRequest(url: url))
}
}
}
extension WKUserScript {
enum Defined: String {
case getUrlAtDocumentStartScript = "GetUrlAtDocumentStart"
case getUrlAtDocumentEndScript = "GetUrlAtDocumentEnd"
var name: String { return rawValue }
private var injectionTime: WKUserScriptInjectionTime {
switch self {
case .getUrlAtDocumentStartScript: return .atDocumentStart
case .getUrlAtDocumentEndScript: return .atDocumentEnd
}
}
private var forMainFrameOnly: Bool {
switch self {
case .getUrlAtDocumentStartScript: return false
case .getUrlAtDocumentEndScript: return false
}
}
private var source: String {
switch self {
case .getUrlAtDocumentEndScript, .getUrlAtDocumentStartScript:
return "webkit.messageHandlers.\(name).postMessage(document.URL)"
}
}
fileprivate func create() -> WKUserScript {
return WKUserScript(source: source,
injectionTime: injectionTime,
forMainFrameOnly: forMainFrameOnly)
}
}
}
extension WKWebViewConfiguration {
func add(script: WKUserScript.Defined, scriptMessageHandler: WKScriptMessageHandler) {
userContentController.addUserScript(script.create())
userContentController.add(scriptMessageHandler, name: script.name)
}
}
ajoutez dans votre paramètre de sécurité de transport Info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Ce n'est peut-être pas une méthode idéale, mais en fonction de votre cas d'utilisation, vous pouvez simplement recharger WKWebView après avoir infecté le script utilisateur:
NSString *scriptSource = @"alert('WKWebView JS Call!')";
WKUserScript *userScript = [[WKUserScript alloc] initWithSource:scriptSource
injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
forMainFrameOnly:YES];
[wkWebView.configuration.userContentController addUserScript:userScript];
[wkWebView reload];
Voici quelque chose qui fonctionne pour moi:
Créez une extension sur WKWebView qui définit une méthode 'runJavaScriptInMainFrame:'. Dans la méthode d'extension, utilisez NSInvocationOperation pour appeler la méthode non documentée '_runJavaScriptInMainFrame:'.
extension WKWebView {
func runJavaScriptInMainFrame(#scriptString: NSString) -> Void {
let selector : Selector = "_runJavaScriptInMainFrame:"
let invocation = NSInvocationOperation(target: self, selector: selector, object: scriptString)
NSOperationQueue.mainQueue().addOperation(invocation)
}
}
Pour l'utiliser, appelez:
webview.runJavacriptInMainFrame:(scriptString: "some javascript code")
Merci à Larsaronen d’avoir fourni le lien vers l’API privée de WKWebView.
Selon la rumeur, il s'agirait d'un bogue, car il existe une fonction privée similaire à celle disponible publiquement dans UIWebView pour évaluer le javascript de obj-C.
Je viens de commencer à explorer l'API WKWebView moi-même, alors ce n'est peut-être pas la meilleure solution, mais je pense que vous pourriez le faire avec le code suivant:
NSString *scriptSource = @"console.log('Hi this is in JavaScript');";
WKUserScript *userScript = [[WKUserScript alloc]
initWithSource:scriptSource
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
forMainFrameOnly:YES];
[myWKController addUserScript:userScript];
(extrait de la conférence WWDC'14)