天天看點

ios webview html互動,前端 WebView 指南之 iOS 互動篇

前文我們介紹了 Android 的 WebView 互動方式,iOS 從原理上來說和 Android 還是非常類似的。在 iOS 中 WebView 需要分為UIWebView 和 iOS8 中新增的 WKWebView 兩種類型。其中 WKWebView 相較于 UIWebView 優勢在于能夠直接使用系統 Safari 渲染引擎去渲染頁面,支援更多的 HTML5 特性,渲染性能也會更好點。由于對 iOS 開發了解不太多,以下的代碼大多是網絡整理,沒有 swift 的實作,如果有任何錯誤還請及時聯系。

用戶端調用 JS

兩個 WebView 類型提供了不同的調用方式,但是基本上可以歸類成以下兩種:

evaluateScript

在 UIWebView 中,iOS7+ 提供了 JavascriptCore 讓我們能夠直接在 WebView 中擷取到 JSContext,也就是目前執行環境的 JS 上下文。在這裡我們就可以擷取到對應的 JS 方法并執行,是非常高效的執行方式。同時這種方式的好處是能夠拿到 JS 執行的結果,并轉換成對應的 JS 類型。定義好 jsContext 之後就可以調用 evaluateScript 方法來執行 JS 了。

- (void)webViewDidFinishLoad:(UIWebView *)webView

{

JSContext *jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

//設定JS執行報錯捕獲

[self.jsContext setExceptionHandler:^(JSContext *context, JSValue *exception){

NSLog(@"%@", exception);

}];

JSValue *value = [self.jsContext evaluateScript:@"document.title"];

self.navigationItem.title = value.toString;

}

Objective-C 資料類型

對應 JavaScript 資料類型

nil

undefined

NSNull

null

NSString

string

NSNumber

number, boolean

NSDictionary

Object object

NSArray

Array object

NSDate

Date object

NSBlock

Function object

id

Wrapper object

Class

Constructor object

不過在 WKWebView 中沒辦法擷取到 JSContext,不過也還是提供了 evaluateScript 方法,調用方式比起 JavascriptCore 更加簡單。同時将錯誤捕獲放置到了執行的異步回調中,對個性化錯誤處理比較友善。

[self.webView evaluateJavaScript:@"document.title" completionHandler:^(id _Nullable title, NSError * _Nullable error) {

NSLog(@"Hello, %@", title);

}];

stringByEvaluatingJavaScriptFromString

除了 evaluateScript,兩個 WebView 還提供了另外一種調用方式,那就是 stringByEvaluatingJavaScriptFromString。同樣是執行一段 JS 字元串,它的優勢是兩者都相容,缺點是傳回值類型無法轉換,隻能是字元串,而且無法捕獲錯誤。

self.navigationItem.title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];

JS 調用用戶端

JS 調用 iOS 用戶端的方法其實和 Android 的非常的類似,JavascriptCore 對應的是 addJavascriptInterface(),而劫持執行的方法都是通用的。

JavascriptCore

不得不說 JavascriptCore 十分強大,擷取到 JSContext 上下文之後既可以讀取 JS 方法,同時也可以對其寫入方法以供 JS 調用。

- (void)webViewDidFinishLoad:(UIWebView *)webView

{

self.jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

self.jsContext[@"hello"] = ^() {

NSLog(@"Hello World");

};

}

這樣加載的頁面中就可以直接執行 hello() 方法來執行用戶端方法了。

WKScriptMessageHandler

雖然在 WKWebView 中不支援擷取 JavascriptCore,但是其提供了一套 Message Handler 協定的方式來進行用戶端與 JS 的通信,和 JavascriptCore 有一些差別。

//定義 Message Handler 處理方法

- (void)userContentController:(WKUserContentController *)userContentController

didReceiveScriptMessage:(WKScriptMessage *)message {

if ([message.name isEqualToString:@"hello"]) {

NSLog(@"Hello World");

}

}

WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];

config.userContentController = [[WKUserContentController alloc] init];

//聲明 hello message handler 協定

[config.userContentController addScriptMessageHandler:self name:@"hello"];

self.webview = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];

self.webview.UIDelegate = self;

[self.view addSubview:self.myWebView]

注冊完 Message Handler 之後,JS 中會存在 window.webkit.messageHandlers 對象,我們可以如下直接調用用戶端方法了。

window.webkit.messageHandlers.hello.postMessage();

URL劫持

同 Android 一樣,我們也可以使用用戶端劫持 URL 跳轉的方式來進行 JS 與用戶端的通信。URL劫持主要是使用 shouldStartLoadWithRequest() 進行 WebView URL 劫持。在該回調中我們能夠擷取到前端提供的 URL 位址。我們通過構造約定協定的 URL 位址提供給用戶端識别,識别成功後執行對應的方法即可。

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {

NSString *requestString = [[[request URL] absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding ];

if ([requestString isEqualToString:@"sdk:hello"]) {

NSLog(@"hello world");

return NO;

}

return YES;

方法劫持

在 WKWebView 中,JS 的 alert() 等彈窗行為方法是無法直接觸發的,它們會觸發用戶端的方法,用戶端需要手動實作這些方法。在這些方法中用戶端可以擷取到 JS 傳入的參數,然後做相應的處理。目前前端主要有以下三種方法會觸發對應的回調方法,對應關系如下:

JS方法

觸發的用戶端方法

alert

runJavaScriptAlertPanelWithMessage

prompt

runJavaScriptTextInputPanelWithPrompt

confirm

runJavaScriptConfirmPanelWithMessage

将這三個方法列在一塊是因為這幾個方法的本質上都是差不多,定義好對應的回調方法即可。用戶端具體的配置如下:

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {

if ([message isEqualToString:@"sdk:hello"]) {

NSLog(@"hello world");

return NO;

}

UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"alert" message:@"JS調用alert" preferredStyle:UIAlertControllerStyleAlert];

[alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {

completionHandler();

}]];

[self presentViewController:alert animated:YES completion:NULL];

}

另外兩種方法都差不多的寫法,這裡就不一一列舉了。在實際的使用過程中我們隻要約定好一種調用協定即可。

總結

本文講述了 JS 調用用戶端的方法,以及用戶端調用前端的方法。JavascriptCore 和 Message Handler 方法都提供了回去執行結果的方法,而 URL 劫持則需要在 JS 調用的時候需要傳入一個回調方法名,然後用戶端直接執行回調方法。這樣就完成了一個完成的資訊交流的過程。

window.hello = function(text){

console.log(text);

};

location.href = '$hello:{"callback": "hello"}';

//以 stringByEvaluatingJavaScriptFromString 為例

[webView stringByEvaluatingJavaScriptFromString:@"hello('hello world')"];

有人将通信機制進行了封裝,形成一套完善的 WebviewJSBridge 方案,提供了用戶端調前端,前端調用用戶端的系統解決方案。例如 marcuswestin/WebViewJavascriptBridge 項目,其實它在底層是使用了 URL 劫持的方法與 JS 進行互動。使用 URL 劫持的方式主要是适用範圍廣,同時還能相容 Android 端。

參考資料: