iOS WebView與Native互動
我們在專案中不可避免的要使用到WebView,一般的用法就是WebView直接載入URL,做一些基本展示操作。但是對於一些特定的需求或邏輯,我們可能就需要WebView傳遞一些資料到Native,由Native來對資料做處理,比如有跨域限制或攔截WebView請求的需求時候。
備註:這裡的Native是相對於JavaScript來說的,是指OC程式碼。
1. UIWebView
我們這裡要做到雙向互動,要求UIWebView載入的Html可呼叫Naitve函式,Native可呼叫Html函式。我在這裡使用的是Apple官方的JavaScriptCore
庫。
1.1 Native呼叫JavaScript
在Native中我們呼叫JS的函式,函式名為showAlert
OC程式碼:
- (void)webViewDidFinishLoad:(UIWebView *)webView{
NSString *result = [webView stringByEvaluatingJavaScriptFromString:@"showAlert('webViewDidFinishLoad')"];
NSLog(@"html返回給native的返回值是:%@",result);
}
對應的JS程式碼:
<script>
function showAlert (str) {
alert(str);
return 'html的返回值';
}
</script>
1.2 JavaScript呼叫Native
OC程式碼:
#import <CoreLocation/CoreLocation.h>
//這裡最主要是建立一個JSContext
JSContext *context = [w valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//通過這個context可以將OC中函式新增到JavaScript的執行環境中
context[@"add"] = ^(NSInteger a, NSInteger b){
return a+b;
};
//這裡就是向JavaScript添加了一個add函式,將JavaScript傳入的兩個引數和返回至呼叫的JavaScript中。
對應的JS程式碼:
<button onclick="alert(add(2,3))">這個是注入module</button>
當然也可以將JSBridage抽象出來,統一在WebView中進行初始化。這樣可以在JS中通過一個物件統一掉用Native的函式,可以避免JavaScript函式與Native的OC函式名衝突.
在OC中這樣寫:
#import <Foundation/Foundation.h>
#import "JSBridgeModule.h"
#import "StringUtil.h"
#import <JavaScriptCore/JavaScriptCore.h>
@implementation JSBridgeModule
- (NSString *)getTime:(NSString *)str{
NSLog(@"getTime native log:%@",str);
return [StringUtil getCurrentTime];
}
- (void)initJSBridge:(JSContext *)jsContext{
[jsContext setObject:self forKeyedSubscript:@"bridge"];//這裡將整個JSBridgeModule賦給了JavaScript執行環境,在JavaScript便可以使用bridge.函式名來呼叫Native函式
jsContext.exceptionHandler = ^(JSContext* context, JSValue* exceptionValue) {
context.exception = exceptionValue;
NSLog(@"js頁面異常了:%@", exceptionValue);
};
}
- (void)callJSFunction:(JSContext *)jsContext{
JSValue *result = [jsContext evaluateScript:@"showAlert('這是native傳遞個html的資料')"];
NSLog(@"html返回給native的返回值是:%@",result);
}
@end
對應的JS使用方法
<button onclick="alert(bridge.getTime('這個是注入module'))">這個是注入module</button>
2. WKWebView
在iOS 8.0中更新了WKWebView來替代UIWebView,WKWebView的效能和效率比UIWebView要強。
這裡有個插曲,,由於我們專案的需要,我們要對WebView載入的Html進行網路請求的攔截,只允許Html中的請求載入按照我們規範的地址。我對UIWebView和WKWebView分別作了研究,註冊WebView的請求delegate是行不通的,我在UIWebView中重寫URlSession來攔截所有的網路請求,這樣可以達到攔截UIWebView請求目的,但是這個方法在WKWebView中行不通,因為WKWebView和當前專案是不同同一個程序中執行的,所以無法攔截WKWebView中的網路請求,所以這條路行不通。我會新寫一篇文章詳細的講解這個問題。
下面言歸正傳,WKWebView中JS與Native的互動
2.1 Native呼叫JavaScript
這裡很簡單,和UIWebView差不多
OC程式碼:
//這是是呼叫JS的showAlert函式,並傳入引數hello
[self.webView evaluateJavaScript:@"showAlert('hello')" completionHandler:nil];
JS程式碼:
<script>
function showAlert (str) {
alert(str);
return 'html的返回值';
}
</script>
2.2 JavaScript呼叫Native
OC程式碼:
向WebView中注入可供JS呼叫物件
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc]init];
WKUserContentController *controller = [[WKUserContentController alloc]init];
[controller addScriptMessageHandler:self name:@"bridge"];
//這裡的bridge物件即為JS呼叫Native的橋接
config.userContentController = controller;
註冊WebView delegate
//JavaScript與Native的互動
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
NSDictionary *body = message.body;
NSLog(@"userContentController message.body:%@",body);
//這裡來判斷JS傳遞過來的資料是否是需要的
if ([message.name isEqualToString:@"xxx"]) {
NSString *method = [body valueForKey:@"function"];
if ([method isEqualToString:@"nativeSetParam"]) {
NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
[userDefault setValue:[body valueForKey:@"value"] forKey:[body valueForKey:@"key"]];
[userDefault synchronize];
}
}
}
對應JS使用程式碼:
<button onclick="postMessage()">JS呼叫Native</button>
//必須通過 xxx.postMessage才能將JS的資料傳送到Native中,Native在回撥中獲取JS傳遞過來的資料
<script>
var bridge = window.webkit.messageHandlers.bridge;
function postMessage(){
alert('要傳送訊息了');
var message = {
'method' : 'hello',
'param1' : 'param',
};
bridge.postMessage(message);
}
</script>