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()//注意,参数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的方法,而在WKWebView中有了改进。具体步骤分为app注册handler,app处理handler委托,js调用三个步骤
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;
WKWebView和cookie
WKWebView的cookie机制非常扯淡,他与UIWebView不同。UIWebView是基于WebKit的,它能自动保存cookie,并且在同一个程序中的各UIWebView中共享,服务器也可以正常的访问cookie。但是WKWebView就完全不是这么回事,首先它不是基于WebKit,而且基于WKWebKit。WKWebKit相对于webkit更加先进,效率高速度快并且内存消耗少,对ios程序带来的好处就是界面更加流畅,不容易内存溢出。但是它的先进性也体现在安全性上,它把cookie保存在私有的域中,只允许服务器和JS写入,不允许后期访问和修改。
所以,想要在ios端访问WKWebView中的cookie,就只能通过注入JS来修改。
这样做虽然可以解决修改cookie的问题,但是也带来了新的问题。比如项目需要JS根据cookie中的字段的值,来改变html的DOM结构,如果采用这种办法,结果就是界面先加载出错误的DOM,然后代码注入js,js再动态地改cookie,最后另一个js函数又加载cookie并改变DOM结构...这样玩那就是多此一举!
所以,若果要用WKWebView,就别想修改cookie了,实在没意义。
针对上述的需求,我又发现了UserAnget这个玩意:
wkWebView.customUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 9_3_2 like Mac OS X) AppleWebKit/600.2.4 (KHTML, like Gecko,device=ios) Mobile/13F67"
只要把自己的字段加在上面这句话的任意一个括号中即可,但是不能在最后面拼接!具体为什么暂时还不知道。
web页面