UIWebView和WKWebView的使用及js交互
web頁面和app直接的交互是很常見的東西,之前嘗試過flex和js的相互調用以及android和js的相互調用,卻只有ios沒試過,據說比較復雜。周末花了點時間研究了一下,確實和其他的不太一樣,但是 也不見復雜。
要知道的事情
ios的webview有2個類,一個叫UIWebView,另一個是WKWebView。兩者的基礎方法都差不多,本文重點是后者,他是取代UIWebView出現的,在app開發者若不需要兼容ios8之前版本,都應該使用WKWebVIew。
WKWebView 是蘋果在 iOS 8 中引入的新組件,目的是給出一個新的高性能的 Web View 解決方案,擺脫過去 UIWebView 的老舊笨重特別是內存占用量巨大的問題,它使用Nitro JavaScript引擎,這意味着所有第三方瀏覽器運行JavaScript將會跟safari一樣快.
ios9默認是不允許加載http請求的,對於webview,加載http網頁也是不允許的。可以通過修改info.plist取消http限制
在項目中找到info.plist,源文件形式打開,添加下面內容
<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict>
dome截圖
大綱
- UIWebView使用
- 加載網頁或本地頁面
- app調js方法
- js調app方法
- WKWebView的使用
- 加載頁面,前進,后退,刷新,進度條
- 前進,后退,刷新,進度條
- js中alert的攔截
- app調js方法
- js調app方法
- webView生命周期和跳轉代理
- web頁面
- 文章demo
- 參考文章
UIWebView使用
UIVebView現在已經棄用,ios8以上都應該用新的WKWebview,所以UIWebView我就隨意說說,大家隨意看看。
加載網頁或本地頁面
//從本地加載html let path:String! = NSBundle.mainBundle().pathForResource("index", ofType: "html") webView.loadRequest(NSURLRequest(URL: NSURL.fileURLWithPath(path))) //從網絡加載 webView.loadRequest(NSURLRequest(URL: NSURL(string: "https://www.bing.com")!))
注意點: 1. ios9默認不能加載http請求,需要聲明 2. uiwebView網絡請求會進入ternal func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool
委托,委托中若return false,也不會繼續加載
app調js方法
app調用js方法使用的是 webView.stringByEvaluatingJavaScriptFromString()
這個方法。它可以直接執行一段js代碼
//調用js無參數的方法 webView.stringByEvaluatingJavaScriptFromString("hi()") //調用js有參數的方法hello(msg) let js = String(format: "hello('%@')", "liuyanwei") webView.stringByEvaluatingJavaScriptFromString(js) //調用js的參數為json對象 let js = String(format: "hello(%@)", "{'obj':'liuyanwei'}") webView.stringByEvaluatingJavaScriptFromString(js) //從文件中加載一段js代碼然后執行 do{ let jsString = try String(contentsOfFile: NSBundle.mainBundle().pathForResource("test", ofType: "js")!, encoding: NSUTF8StringEncoding) self.webView.stringByEvaluatingJavaScriptFromString(jsString) } catch{} //直接執行alert webView.stringByEvaluatingJavaScriptFromString("alert('hi')") //執行有返回值的js函數 NSLog("%@", webView.stringByEvaluatingJavaScriptFromString("getName()")!)
相關的js代碼
var hi = function(){ alert("hello") $(".info").html("hi"); } var hello = function(msg){ alert("hello " + msg) if(msg.obj != undefined) alert(msg.obj) } var getName = function(){ return "liuyanwei" }
js調app方法
很多人覺得,為什么UIWebView中,js調用app的方式怎么那么奇怪,其實應該這樣說,UIWebView沒有辦法直接使用js調用app,但是可以通過攔截request的方式間接實現js調用app方法。
既然是攔截url,那你就可以任意方式去規定想要調用的url的路徑和app中方法轉換的方式。我這里使用協議和路徑的方式,例如我攔截到的url是 “hello://hello_liuyanwei” ,我就把hello當做想調用的方法,路徑當做參數。這種方式不一定好,但是使用起來還是挺方便的。
js中調用app的方法如下:
//這段代碼是原生js代碼,在js中的作用是做頁面跳轉 //webView通過攔截url請求方式攔截到request,通過解析從而調用 ios hello方法,參數是hello_liuyanwei document.location = "hello://hello_liuyanwei";
//webView 需要實現UIWebViewDelegate委托方法 // class ViewController: UIViewController,UIWebViewDelegate .... // webView.delegate = self func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool{ //如果請求協議是hello 這里的hello來自js的調用,在js中設為 document.location = "hello://liuyanwei 你好"; //scheme:hello ,msg:liuyanwei 你好 //通過url攔截的方式,作為對ios原生方法的呼叫 if request.URL?.scheme == "hello"{ let method:String = request.URL?.scheme as String! let sel = Selector(method+":") self.performSelector(sel, withObject:request.URL?.host) request.URL?.path //如果return true ,頁面加載request,我們只是當做協議使用所以不能頁面跳轉 return false } return true }
最后說一下關於js調用app的返回值。app調js可以有返回值,但是js調app是通過間接的攔截request方式實現,它根本就不算方法調用,所以應該是不存在可以直接產生返回值的(如果不對歡迎指正)。當然如果需要app對js的調用有所響應,可以通過回叫函數的方式回應js。可以在調用app的時候增加一個js回叫函數名 app在處理完之后可以呼叫回叫函數並把需要的參數通過回叫函數的方式進行傳遞。
WKWebView的使用
WKWebVIew是UIWebView的代替品,新的WebKit框架把原來的功能拆分成許多小類。本例中主要用到了WKNavigationDelegate,WKUIDelegate,WKScriptMessageHandler三個委托和配置類WKWebViewConfiguration去實現webView的request控制,界面控制,js交互,alert重寫等功能。 使用WKWebView需要引入#import <WebKit/WebKit.h>
加載頁面,配置委托和手勢等
//加載頁面 config = WKWebViewConfiguration() //設置位置和委托 webView = WKWebView(frame: self.webWrap.frame, configuration: config) webView.navigationDelegate = self webView.UIDelegate = self self.webWrap.addSubview(webView) //加載網頁 //webView.loadRequest(NSURLRequest(URL: NSURL(string: "https://www.bing.com")!)) //加載本地頁面 webView.loadRequest(NSURLRequest(URL: NSURL.fileURLWithPath(NSBundle.mainBundle().pathForResource("index", ofType: "html")!))) //允許手勢,后退前進等操作 webView.allowsBackForwardNavigationGestures = true
前進,后退,刷新,進度條
//前進 webView.goBack() //后退 webView.goForward() //刷新 let request = NSURLRequest(URL:webView.URL!) webView.loadRequest(request) //監聽是否可以前進后退,修改btn.enable屬性 webView.addObserver(self, forKeyPath: "loading", options: .New, context: nil) //監聽加載進度 webView.addObserver(self, forKeyPath: "estimatedProgress", options: .New, context: nil) //重寫self的kvo方法 override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) { if (keyPath == "loading") { gobackBtn.enabled = webView.canGoBack forwardBtn.enabled = webView.canGoForward } if (keyPath == "estimatedProgress") { //progress是UIProgressView progress.hidden = webView.estimatedProgress==1 progress.setProgress(Float(webView.estimatedProgress), animated: true) } }
js中alert的攔截
在WKWebview中,js的alert是不會出現任何內容的,你必須重寫WKUIDelegate
委托的runJavaScriptAlertPanelWithMessage message
方法,自己處理alert。類似的還有Confirm和prompt也和alert類似,這里我只以alert為例。
//alert捕獲 func webView(webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: () -> Void) { // completionHandler() let alert = UIAlertController(title: "ios-alert", message: "\(message)", preferredStyle: .Alert) alert.addAction(UIAlertAction(title: "ok", style: .Default, handler:nil)) alert.addAction(UIAlertAction(title: "cancel", style: .Cancel, handler: nil)) self.presentViewController(alert, animated: true, completion: nil) }
app調js方法
WKWebView調用js方法和UIWebView類似,一個是evaluateJavaScript,一個是stringByEvaluatingJavaScriptFromString。獲取返回值的方式不同,WKWebView用的是回叫函數獲取返回值
//直接調用js webView.evaluateJavaScript("hi()", completionHandler: nil) //調用js帶參數 webView.evaluateJavaScript("hello('liuyanwei')", completionHandler: nil) //調用js獲取返回值 webView.evaluateJavaScript("getName()") { (any,error) -> Void in NSLog("%@", any as! String) }
js調app方法
UIwebView沒有js調app的方法主要有2種實現,一種是通過攔截request的方式間接實現,另一種是使用JavaScriptCore的jsContext注冊objc對象或使用JSExport協議導出Native對象的方式。本文主要介紹第一種實現,第二種實現方式參考后續文章 JavaScriptCore的使用教程
1:注冊handler需要在webView初始化之前,如示例,注冊了一個webViewApp的handler
config = WKWebViewConfiguration() //注冊js方法 config.userContentController.addScriptMessageHandler(self, name: "webViewApp") webView = WKWebView(frame: self.webWrap.frame, configuration: config)
2:處理handler委托。ViewController實現WKScriptMessageHandler委托的func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage)
方法
//實現WKScriptMessageHandler委托 class ViewController:WKScriptMessageHandler //實現js調用ios的handle委托 func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) { //接受傳過來的消息從而決定app調用的方法 let dict = message.body as! Dictionary<String,String> let method:String = dict["method"]! let param1:String = dict["param1"]! if method=="hello"{ hello(param1) } }
3:js調用
通過 window.webkit.messageHandlers.webViewApp找到之前注冊的handler對象,然后調用postMessage方法把數據傳到app,app通過上一步的方法解析方法名和參數
var message = { 'method' : 'hello', 'param1' : 'liuyanwei', }; window.webkit.messageHandlers.webViewApp.postMessage(message);
如果需要app對js的調用有所響應,可以通過回叫函數的方式回應js。可以在調用app的時候增加一個js回叫函數名 app在處理完之后可以呼叫回叫函數並把需要的參數通過回叫函數的方式進行傳遞
webView生命周期和跳轉代理
該代理提供的方法,可以用來追蹤加載過程(頁面開始加載、加載完成、加載失敗)、決定是否執行跳轉
// 頁面開始加載時調用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation;
// 當內容開始返回時調用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation;
// 頁面加載完成之后調用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation;
// 頁面加載失敗時調用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation;
// 接收到服務器跳轉請求之后調用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation;
// 在收到響應后,決定是否跳轉
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;
// 在發送請求之前,決定是否跳轉
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
web頁面
隨便說兩句web頁面,demo中的web前段用了jquery去操作dom,btn是在js中添加的,btn的點擊事件也在js中。另外隨便寫了幾個css讓頁面稍微美觀一些。web頁面都在項目文件夾下的web文件夾中。大家其實也不必看,因為調用的app的js代碼在文中都有單獨貼出。
參考文章
本文也只是用了一些基本的用法,大家想了解更多,可以看看下面的三篇文章做補充閱讀。但是現在也沒發現有把webView這塊寫的很全很詳細的文章,今后要是看見我會繼續補充在這里。
demo
我博客中大部分示例代碼都上傳到了github,地址是:https://github.com/coolnameismy/demo,點擊跳轉代碼下載地址
本文代碼存放目錄是ios-WebView,本demo沒做界面自適應,為了保證效果請用iPhone6及以上模擬器打開
感謝收看,如果對大家有幫助,請github上follow和star,本文發布在劉彥瑋的技術博客,轉載請注明出處
dome截圖