1. 程式人生 > >iOS WebView與Native互動

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>