webview和H5交互

因爲H5的靈活多變,動態可配的特色,也爲了不冗長 的審覈週期,H5頁面在app上的重要性正日益突顯。javascript

iOS應用於H5交互的控件主要是UIWebView及WKWebViewjava

WKWebView是14年隨iOS8推出的,很好的解決了UIWebView加載速度慢,內存佔用大的問題git

WebViewJavaScriptBridge是一款輕量級的框架,使用它結合wkwebview能十分方便的實現源生與H5的交互github

  webviewJavaScrptBridge的基本使用web

  1.初始化需bind視圖  [WebViewJavaScriptBridge bridgeForWebView:]ajax

  2.設置代理  [self.bridge setWebViewDelegate:self]api

  3.註冊方法  網絡

  [self.bridge registerHandler:@"click" handler:^(id data, WVJBResponseCallback responseCallback) {閉包

        NSDictionary *dic = (NSDictionary *)data;app

        [weakSelf responseJSWithData:(NSDictionary *)dic];

     }];

  click是方法名,handler是H5發起調用後傳回的回調,該閉包第一個H5頁面傳遞過來的參數,第二個是callBack對象

  4.調用方法  

  [self.bridge callHandler:event data:data responseCallback:^(id responseData) {

        NSLog(@"responseData:%@",responseData);

     }];

  event是方法名,源生直接發起回調,data是傳遞的參數,callBack是h5收到事件後傳回的回調

 

  webviewJavaScriptBridge的精髓就是方法3,4的交替使用,使得源生調h5,h5調源生變的異常簡單,二者之間的連接僅僅靠一個方法名,收方register後,發起方callHandler就能實現一條消息的有效傳遞,具體實現細節可細究其源碼  webviewJavaScriptBridge

  如今來探究下H5端如何使用bridge註冊及發起事件

/// 配置bridge
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 = 'https://__bridge_loaded__';
        document.documentElement.appendChild(WVJBIframe);
        setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
    }

/// callHandler    data爲H5傳遞參數
setupWebViewJavascriptBridge(function (bridge) {
            let data = {'title': title}
            bridge.callHandler('setTitle', data)
        })

/// registerHandler    data爲源生傳遞參數
bridge.registerHandler("result", function (data, responseCallback) {
            result(data['opType'], data['code'], data['msg'])
        })

   可見在H5端使用bridge完成消息的收發頁十分方便。webviewJavaScriptCoreBridge的配置,是在H5頁面生成一個iframe節點,傳遞消息時插入這個節點,結束後移除這個節點,以此來實現源生到H5的一次消息傳遞。

 

WKNavigationDelegate  使用wkwebview進行一次網絡請求中的各類事件回調

WKUIDelegate   主要用於處理H5中的alert彈窗事件

  -runJavaScriptAlertPanelWithMessage:

 

UIWebView,儘管該視圖已經被WKWebview取代,但市面上大多數應用框架仍然使用的是webview,而且其更接近底層,具備深究價值

UIWebview一般是經過攔截request,根據指定url中的參數,來實現H5端事件的調用

func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool
    {
        let  absoluteString = request.url!.absoluteString
		if (!absoluteString.contain(prefix)) {
			return true
		}else {
              // do something return false } }

返回false則攔截請求,截取url中所帶參數,根據需求作相應處理。一般爲封裝方法間的調用,會插入一段js代碼。

override func webViewDidStartLoad(_ webView: UIWebView) {
        super.webViewDidStartLoad(webView)
        
        /// 儘量在較早的時間點插入該js代碼
        writtenJSApi(webView: webView)
    }

func writtenJSApi(webView:UIWebView) {
        if  let filePath:String = Bundle.main.path(forResource: "jsapi", ofType: "js"){
            if let jsStr = try? NSString(contentsOfFile: filePath, encoding: String.Encoding.utf8.rawValue){
                webView.stringByEvaluatingJavaScript(from: jsStr as String)
            }
        }
    }

 js主要代碼

// 異步
 window.JSApi.asyncInvoke = function (apiname, pramaJsonString, callbackapi)
 {
 var apiPath = "https://asyncjsapi.com/" + encodeURIComponent(apiname + window.JSApi.pramaSplit + pramaJsonString + window.JSApi.pramaSplit + callbackapi);
 var iframe = document.createElement("iframe");
 iframe.setAttribute("src", apiPath);
 document.documentElement.appendChild(iframe);
 iframe.parentNode.removeChild(iframe);
 iframe = null;
 }
 
 // 同步
 window.JSApi.syncInvoke = function (apiname, pramaJsonString)
 {
 var apiPath = "https://syncjsapi.com/" + encodeURIComponent(apiname + window.JSApi.pramaSplit + pramaJsonString );
 var request = new XMLHttpRequest();
 if (request == null)
 {
 return {
 code: 1,
 errorMsg: "not support XMLHttpRequest",
 data: null
 };
 }
 
 var responseText = ""
 
 request.onreadystatechange = function ()
 {
 // alert("stateChange"+request.readyState+","+request.status)
 if (request.readyState == 4 && request.status == 200)
 {
 responseText = request.responseText;
 }
 else
 {
 responseText = ""
 }
 // alert(responseText)
 };
 // alert(apiname+"  2  request open    "+apiPath);
 request.open("GET", apiPath, false);
 request.send(null);
 
 console.log(responseText)
 
 return responseText
 }
 })();

 能夠看出,異步方法依然是在頁面建立iframe,先插入後刪除的方式;同步方法是利用ajax,建立了一個http請求,並監聽request的狀態,當status爲200時,返回信息給H5。同步方法的攔截是在urlProtocol裏面

class JsApiURLProtocol: URLProtocol, URLSessionDataDelegate, URLSessionTaskDelegate {

	override class func canInit(with request: URLRequest) -> Bool {

		if let strUrl:String = request.url?.absoluteString
        {
            if JsApiHelper.isSyncNativeApi(urlString: strUrl) {
                return true
            }
        }else {
        	return false
        }

	}

	override func startLoading() {
        let strUrl:String! = request.url!.absoluteString
        if JsApiHelper.isSyncNativeApi(urlString: strUrl) {
            // 調用同步jsapi
            let result: NSString = JsApiHelper.syncInvoke(urlString: strUrl! as NSString) as NSString
            let resultData: NSData = result.data(using: String.Encoding.utf8.rawValue)! as NSData
            var headerFields: [NSObject : AnyObject]? = [NSObject : AnyObject]()
            headerFields?.updateValue("*" as AnyObject, forKey: "Access-Control-Allow-Origin" as NSObject)
            
            let response: HTTPURLResponse = HTTPURLResponse(url: self.request.url!, statusCode: 200, httpVersion: "HTTP/1.1", headerFields: headerFields as? [String:String])!
            self.client!.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
            self.client!.urlProtocol(self, didLoad: resultData as Data)
            self.client!.urlProtocolDidFinishLoading(self)
        }
    }

}

 caninit方法中return true表示進行請求,不然攔截請求。startLoading方法中建立相應的response返回給js中的request,注意finishLoading,不然容易形成死循環。

 

UIWebview調用H5就至關簡單了,一句代碼搞定

  webView.stringByEvaluatingJavaScript(from: "javascript:window.goBack()");

相關文章
相關標籤/搜索