OC与JS交互之WKWebView
阅读目录
- 一、WKWebView Framework
- 二、WKWebView中的三个代理方法
- 三、使用WKWebView重写
- 四、后记
WKWebView的14个类与3个协议:
WKBackForwardList: 之前访问过的 web 页面的列表,可以通过后退和前进动作来访问到。
WKBackForwardListItem: webview 中后退列表里的某一个网页。
WKFrameInfo: 包含一个网页的布局信息。
WKNavigation: 包含一个网页的加载进度信息。
WKNavigationAction: 包含可能让网页导航变化的信息,用于判断是否做出导航变化。
WKNavigationResponse: 包含可能让网页导航变化的返回内容信息,用于判断是否做出导航变化。
WKPreferences: 概括一个 webview 的偏好设置。
WKProcessPool: 表示一个 web 内容加载池。
WKUserContentController: 提供使用 JavaScript post 信息和注射 script 的方法。
WKScriptMessage: 包含网页发出的信息。
WKUserScript: 表示可以被网页接受的用户脚本。
WKWebViewConfiguration: 初始化 webview 的设置。
WKWindowFeatures: 指定加载新网页时的窗口属性。
WKWebsiteDataStore: 包含网页数据存储和查找。
WKNavigationDelegate: 提供了追踪主窗口网页加载过程和判断主窗口和子窗口是否进行页面加载新页面的相关方法。
WKUIDelegate: 提供用原生控件显示网页的方法回调。
WKScriptMessageHandler: 提供从网页中收消息的回调方法。
二、WKWebView中的三个代理方法
- WKNavigationDelegate
该代理提供的方法,可以用来追踪加载过程(页面开始加载、加载完成、加载失败)、决定是否执行跳转。
// 页面开始加载时调用
- (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;
- WKUIDelegate
创建一个新的WKWebView
// 创建一个新的WebView
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures;
剩下三个代理方法全都是与界面弹出提示框相关的,针对于web界面的三种提示框(警告框、确认框、输入框)分别对应三种代理方法。
// 界面弹出警告框
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(void (^)())completionHandler;
// 界面弹出确认框
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler;
// 界面弹出输入框
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler;
- WKScriptMessageHandler
这个协议中包含一个必须实现的方法,这个方法是native与web端交互的关键,它可以直接将接收到的JS脚本转为OC或Swift对象。
// 从web界面中接收到一个脚本时调用
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
三、使用WKWebView
我这里加载的是本地的html,我先贴出html的代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" http-equiv="Content-Type" content="text/html">
<title>小红帽</title>
<style>
*{
font-size: 50px;
}
.div{
align:"center";
}
.btn{
height:80px; width:80%; padding: 0px 30px 0px 30px; background-color: #0071E7; border: solid 1px #0071E7; border-radius:5px; font-size: 1em; color: white
}
</style>
<script>
function clear(){
document.getElementById('mobile').innerHTML=''
document.getElementById('name').innerHTML=''
document.getElementById('msg').innerHTML=''
}
//oc调用js的方法列表
function alertMobile(){
//这里已经调用过来了 但是搞不明白为什么alert方法没有响应
//alert('我是上面的小黄 手机号是:13300001111')
document.getElementById('mobile').innerHTML='我是上面的小黄 手机号是:13300001111'
}
function alertName(msg){
document.getElementById('name').innerHTML='你好 ' + msg + ', 我也很高兴见到你'
}
function alertSendMsg(num,msg){
document.getElementById('msg').innerHTML='这是我的手机号:' + num + ',' + msg + '!!'
}
//JS响应方法列表
function btnClick1(){
window.webkit.messageHandlers.showMobile.postMessage(null)
//window.webkit.messageHandlers.showMobile.postMessage(null)
}
function btnClick2(){
window.webkit.messageHandlers.showName.postMessage('xiao黄')
}
function btnClick3(){
window.webkit.messageHandlers.showSendMsg.postMessage(['13300001111', 'Go Climbing This Weekend !!!'])
}
</script>
</head>
<body>
<br/>
<div>
<label>自己写html</label>
</div>
<br/>
<div id="mobile"></div>
<div class="div">
<button class="btn" type="button" onclick="btnClick1()">小红帽的手机号</button>
</div>
<br/>
<div id="name"></div>
<div class="div">
<button class="btn" type="button" onclick="btnClick2()">打电话给小红帽</button>
</div>
<br/>
<div id="msg"></div>
<div class="div">
<button class="btn" type="button" onclick="btnClick3()">发短信给小红帽</button>
</div>
<br/>
</body>
</html>
关于html的内容,我在这里不多加解释,有兴趣的同学可以去学习一下关于h5,css,javascript的相关知识。
WKWebView不支持nib文件,所以这里需要使用代码初始化并加载WebView
/*设置configur对象的WKUserContentController属性的信息,也就是设置js可与webview内容交互配置
1、通过这个对象可以注入js名称,在js端通过window.webkit.messageHandlers.自定义的js名称.postMessage(如果有参数可以传递参数)方法来发送消息到native;
2、我们需要遵守WKScriptMessageHandler协议,设置代理,然后实现对应代理方法(userContentController:didReceiveScriptMessage:);
3、在上述代理方法里面就可以拿到对应的参数以及原生的方法名,我们就可以通过NSSelectorFromString包装成一个SEL,然后performSelector调用就可以了
4、以上内容是WKWebview和UIWebview针对JS调用原生的方法最大的区别(UIWebview中主要是通过是否允许加载对应url的那个代理方法,通过在js代码里面写好特殊的url,然后拦截到对应的url,进行字符串的匹配以及截取操作,最后包装成SEL,然后调用就可以了)
*/
/*
上述是理论说明,结合下面的实际代码再做一次解释,保你一看就明白
1、通过addScriptMessageHandler:name:方法,我们就可以注入js名称了,其实这个名称最好就是跟你的方法名一样,这样方便你包装使用,我这里自己写的就是openBigPicture,对应js中的代码就是window.webkit.messageHandlers.openBigPicture.postMessage()
2、因为我的方法是有参数的,参数就是图片的url,因为点击网页中的图片,要调用原生的浏览大图的方法,所以你可以通过字符串拼接的方式给"openBigPicture"拼接成"openBigPicture:",我这里没有采用这种方式,我传递的参数直接是字典,字典里面放了方法名以及图片的url,到时候直接取出来用就可以了
3、我的js代码中关于这块的代码是
window.webkit.messageHandlers.openBigPicture.postMessage({methodName:"openBigPicture:",imageSrc:imageArray[this.index].src});
4、js和原生交互这块内容离不开
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{}这个代理方法,这个方法以及参数说明请到下面方法对应处
*/
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc]init];
//设置configur对象的preferences属性的信息
config.preferences.minimumFontSize = 18;
WKWebView *wkView = [[WKWebView alloc]initWithFrame:CGRectMake(0, 0, 414, 735/2) configuration:config];
wkView.navigationDelegate=self;
[self.view addSubview:wkView];
self.wkwebView = wkView;
NSString *filePath=[[NSBundle mainBundle] pathForResource:@"myindex" ofType:@"html"];
NSURL *baseUrl=[[NSBundle mainBundle] bundleURL];
[self.wkwebView loadHTMLString:[NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil] baseURL:baseUrl];
WKUserContentController *userController=config.userContentController;
//JS调用OC 添加处理脚本
[userController addScriptMessageHandler:self name:@"showMobile"];
[userController addScriptMessageHandler:self name:@"showName"];
[userController addScriptMessageHandler:self name:@"showSendMsg"];
在代理方法中处理相关的操作,js调用oc的代码
//js调用oc,通过这个代理方法进行拦截
/*
1、js调用原生的方法就会走这个方法
2、message参数里面有2个参数我们比较有用,name和body,
2.1 :其中name就是之前已经通过addScriptMessageHandler:name:方法注入的js名称
2.2 :其中body就是我们传递的参数了,比如说我在js端传入的是一个字典,所以取出来也是字典
*/
-(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
if ([message.name isEqualToString:@"showMobile"]) {
[self alertMessage:@"这是下面的小红帽 手机号 123333333"];
}
if ([message.name isEqualToString:@"showName"]) {
NSString *info=[NSString stringWithFormat:@"%@",message.body];
[self alertMessage:info];
}
if ([message.name isEqualToString:@"showSendMsg"]) {
NSArray *arr=message.body;
NSString *info=[NSString stringWithFormat:@"%@%@",arr.firstObject,arr.lastObject];
[self alertMessage:info];
}
}
-(void)alertMessage:(NSString *)msg{
UIAlertController *alertController=[UIAlertController alertControllerWithTitle:@"信息" message:msg preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *ok=[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
}];
[alertController addAction:ok];
[self presentViewController:alertController animated:YES completion:^{
}];
}
下面的是oc调用js,分开写主要是为了让大家看清楚每部分的代码,已经相关的解释
- (IBAction)clearBtn:(id)sender {
[self.wkwebView evaluateJavaScript:@"clear()" completionHandler:nil];
}
//oc调用js,通过evaluateJavaScript:注入方法名
- (IBAction)clickBtnItem:(UIButton *)sender {
switch (sender.tag) {
case 100:
{
[self.wkwebView evaluateJavaScript:@"alertMobile()" completionHandler:nil];
}
break;
case 101:
{
[self.wkwebView evaluateJavaScript:@"alertName('小红毛')" completionHandler:nil];
}
break;
case 102:
{
[self.wkwebView evaluateJavaScript:@"alertSendMsg('18870707070','周末爬山真是件愉快的事情')" completionHandler:nil];
}
break;
default:
break;
}
}
这是我拿出html里面javaScript的代码,供大家阅读,里面我都有标注的注释。
<script>
function clear(){
document.getElementById('mobile').innerHTML=''
document.getElementById('name').innerHTML=''
document.getElementById('msg').innerHTML=''
}
//oc调用js的方法列表
function alertMobile(){
//这里已经调用过来了 但是搞不明白为什么alert方法没有响应
//alert('我是上面的小黄 手机号是:13300001111')
document.getElementById('mobile').innerHTML='我是上面的小黄 手机号是:13300001111'
}
function alertName(msg){
document.getElementById('name').innerHTML='你好 ' + msg + ', 我也很高兴见到你'
}
function alertSendMsg(num,msg){
document.getElementById('msg').innerHTML='这是我的手机号:' + num + ',' + msg + '!!'
}
//JS响应方法列表
function btnClick1(){
window.webkit.messageHandlers.showMobile.postMessage(null)
//window.webkit.messageHandlers.showMobile.postMessage(null)
}
function btnClick2(){
window.webkit.messageHandlers.showName.postMessage('xiao黄')
}
function btnClick3(){
window.webkit.messageHandlers.showSendMsg.postMessage(['13300001111', 'Go Climbing This Weekend !!!'])
}
</script>
四、学习总结
到此,关于js和原生的交互系列的示例已完成,过程中遇到很多坑,但也很有收获,关于我的描述不清,或者不妥的地方,请大家指出。每篇文章都会对知识点进行总结,在文章末尾给出相关链接和示例DEMO的地址,同样本文的示例也已放在GitHub上,需要的同学取走不谢。关于这几篇文章的DEMO,对比学习,在看的过程中有什么呢疑问,欢迎在下面留言,若发现文章中有那些地方没有阐述清,或者没有提到也可以留言告诉我,我们公司最近一直是h5和原生的app想结合,所以研究一段时间。随着H5的强大,hybrid app已经成为当前互联网的大方向,单纯的native app和web app在某些方面显得就很劣势。
作者:默默的前行
链接:https://www.jianshu.com/p/82af19c5fc6e