創建WKWebView。
這一步,唯一需要注意的地方,就是不用再設置WKWebView 的navigationDelegate,下一步你就知道為什么了
- (void)initWKWebView { WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init]; configuration.userContentController = [WKUserContentController new]; WKPreferences *preferences = [WKPreferences new]; preferences.javaScriptCanOpenWindowsAutomatically = YES; preferences.minimumFontSize = 30.0; configuration.preferences = preferences; self.webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration]; NSString *urlStr = [[NSBundle mainBundle] pathForResource:@"index.html" ofType:nil]; NSString *localHtml = [NSString stringWithContentsOfFile:urlStr encoding:NSUTF8StringEncoding error:nil]; NSURL *fileURL = [NSURL fileURLWithPath:urlStr]; [self.webView loadHTMLString:localHtml baseURL:fileURL]; self.webView.UIDelegate = self; [self.view addSubview:self.webView]; }
創建WebViewJavascriptBridge實例。
WKWebView 使用的是WKWebViewJavascriptBridge,而UIWebView 使用的是WebViewJavascriptBridge
_webViewBridge = [WKWebViewJavascriptBridge bridgeForWebView:self.webView]; // 如果控制器里需要監聽WKWebView 的`navigationDelegate`方法,就需要添加下面這行。 [_webViewBridge setWebViewDelegate:self];
上一步說了不用再設置WKWebView 的navigationDelegate,那是因為在{-bridgeForWebView:}內已經將WKWebView 的navigationDelegate設置為WKWebViewJavascriptBridge的實例了。
+ (instancetype)bridgeForWebView:(WKWebView*)webView { WKWebViewJavascriptBridge* bridge = [[self alloc] init]; [bridge _setupInstance:webView]; [bridge reset]; return bridge; } - (void) _setupInstance:(WKWebView*)webView { _webView = webView; _webView.navigationDelegate = self; _base = [[WebViewJavascriptBridgeBase alloc] init]; _base.delegate = self; }
注冊 js 要調用的Native 功能
為了便於維護,我將所有js 要調用Native 功能放在了一個方法里添加,然后每個功能再單獨處理。
示例代碼如下:
#pragma mark - private method - (void)registerNativeFunctions { [self registScanFunction]; [self registShareFunction]; [self registLocationFunction]; [self regitstBGColorFunction]; [self registPayFunction]; [self registShakeFunction]; } // 注冊的獲取位置信息的Native 功能 - (void)registLocationFunction { [_webViewBridge registerHandler:@"locationClick" handler:^(id data, WVJBResponseCallback responseCallback) { // 獲取位置信息 NSString *location = @"廣東省深圳市"; // 將結果返回給js responseCallback(location); }]; }
- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler,我們可以這樣理解,后面的block 參數是js 要調用的Native 實現,前面的handlerName 是這個Native 實現的別名。然后js 里調用handlerName 這個別名,
WebViewJavascriptBridge最終會執行block 里的Native 實現。
在HTML添加關鍵的js
HMTL 里在調用Native 功能之前,要先添加一個js 方法,然后主動調用一次該方法。
要添加的方法是:
function setupWebViewJavascriptBridge(callback) { if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); } if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); } window.WVJBCallbacks = [callback]; var WVJBIframe = document.createElement('iframe'); WVJBIframe.style.display = 'none'; WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__'; document.documentElement.appendChild(WVJBIframe); setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0) }
然后在js 中要主動調用一次上述的setupWebViewJavascriptBridge
setupWebViewJavascriptBridge(function(bridge) { // 這里注冊Native 要調用的js 功能。 bridge.registerHandler('testJSFunction', function(data, responseCallback) { alert('JS方法被調用:'+data); responseCallback('js執行過了'); }) // 如果要有其他Native 調用的js 功能,在這里按照上面的格式添加。 })
主動調用setupWebViewJavascriptBridge有兩個目的:
1、執行一次wvjbscheme://__BRIDGE_LOADED__請求。
2、注冊Native 要調用的js 功能。
執行wvjbscheme://__BRIDGE_LOADED__,然后在WKWebView 的navigationDelegate方法中攔截該URL ,然后往HMTL中注入js。以下源碼都摘自WebViewJavascriptBridge:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { if (webView != _webView) { return; } NSURL *url = navigationAction.request.URL; __strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate; if ([_base isCorrectProcotocolScheme:url]) { // 在這里攔截wvjbscheme://__BRIDGE_LOADED__ if ([_base isBridgeLoadedURL:url]) { // 這里會注入js [_base injectJavascriptFile]; } else if ([_base isQueueMessageURL:url]) { [self WKFlushMessageQueue]; } else { [_base logUnkownMessage:url]; } decisionHandler(WKNavigationActionPolicyCancel); } if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationAction:decisionHandler:)]) { [_webViewDelegate webView:webView decidePolicyForNavigationAction:navigationAction decisionHandler:decisionHandler]; } else { decisionHandler(WKNavigationActionPolicyAllow); } } - (void)injectJavascriptFile { //讀取js 內容 NSString *js = WebViewJavascriptBridge_js(); // 執行Native 的API,實現將js 注入 到HMTL中。 [self _evaluateJavascript:js]; if (self.startupMessageQueue) { NSArray* queue = self.startupMessageQueue; self.startupMessageQueue = nil; for (id queuedMessage in queue) { [self _dispatchMessage:queuedMessage]; } } }
WKWebView 執行js 的API 與 UIWebView 有些不同,WKWebView 用的是{-evaluateJavaScript: completionHandler:},這個API 不會立刻返回執行結果,js 的執行結果會在block 中返回。
在js 中調用 Native 功能。
終於到了 js 調用Native 的用法了。其實非常的簡單,例如我想要利用Native 獲取定位信息,那么在HTML中添加一個按鈕,onclick事件是locationClick(),按照如下實現即可:
function locationClick() { WebViewJavascriptBridge.callHandler('locationClick',null,function(response) { alert(response); document.getElementById("returnValue").value = response; }); }
Native 執行完代碼,將獲取到的定位信息,通過callHandler 的第三方參數,回調返回到js 中。
response 可以是單個值,也可以是數組、鍵值對等。
當然如果我們調用Native 的時候,沒有參數或者不需要Native 返回信息到js 中。我們還可以這樣寫:
// 沒有參數,有回調可以這樣寫 function locationClick() { WebViewJavascriptBridge.callHandler('locationClick',function(response) { alert(response); document.getElementById("returnValue").value = response; }); } // 沒有參數,又不需要回調可以這樣寫 function shake() { WebViewJavascriptBridge.callHandler('shakeClick'); }
至此,JS 通過WebViewJavascriptBridge調用Native 的功能就完成了。
Native 調用 JS 功能。
Native 調用js 功能與 js 調用Native 的原理和流程一樣。
1、現在js 中注冊,Native 要調用的功能。
2、Native 調用注冊時,該功能的別名,就可以完成調用。
在js 中注冊 Native 要調用的功能,同樣需要為該功能設置一個別名HandlerName。
其實這個步驟在前面介紹過,代碼如下:
setupWebViewJavascriptBridge(function(bridge) { // 這里注冊Native 要調用的js 功能。 bridge.registerHandler('testJSFunction', function(data, responseCallback) { alert('JS方法被調用:'+data); responseCallback('js執行過了'); }) // 如果要有其他Native 調用的js 功能,在這里按照上面的格式添加。 })
testJSFunction的js功能,第二個參數是一個function。function里的data ,就是Native 調用該功能時傳過來的參數,responseCallback是執行完js 代碼后,通過responseCallback將必要的信息返回到Native中。
- (void)rightClick { // // 如果不需要參數,不需要回調,使用這個 // [_webViewBridge callHandler:@"testJSFunction"]; // // 如果需要參數,不需要回調,使用這個 // [_webViewBridge callHandler:@"testJSFunction" data:@"一個字符串"]; // 如果既需要參數,又需要回調,使用這個 [_webViewBridge callHandler:@"testJSFunction" data:@"一個字符串" responseCallback:^(id responseData) { NSLog(@"調用完JS后的回調:%@",responseData); }]; }
WKWebView 通過WebViewJavascriptBridge實現js 與Native 的交互,到這里就已經完成了。
demo地址: https://github.com/domanc/JS_OC_WebViewJavascriptBridge_UIWebView.git
