前言
在 iOS
开发中,iOS
和 JS
交互是每个程序猿必须掌握的技能。iOS8
以后,苹果推出了新框架 WebKit
,使用 WKWebView
替代 UIWebView
。稳定性好、占用内存少,速度更快
。
说道 iOS
和 JS
交互,就不得不提 Hybrid(Hybrid Mobile App)
,即通过 Web
网络技术与 Native
相结合的混合移动应用开发
WKWebView
特性
1、在性能、稳定性、功能方面有很大提升,直观体现是内存占用变少;
2、高达60fps的滚动刷新率以及内置手势
3、支持了更多的HTML5特性;
本文主要介绍 WKWebView
与 JS
交互
WKWebView 和 JS 交互的方法
- 拦截
URL
WKScriptMessageHandler
- WebViewJavascriptBridge等其他第三方框架
下面以实际功能为例讲解其使用
1.拦截 URL 实现自定义跳转功能
1、和后端协定好 协议
2、通过 WKWebView
的 WKNavigationDelegate
代理回调 decidePolicyForNavigationAction
方式实现拦截 URL
,例如:点击WebView按钮、cell等事件,去做一些功能
1 2 3 4 5 6
| - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{ // 获取当前的url NSString * url = navigationAction.request.URL.absoluteString; ZJLog(@"url:%@",url); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #pragma mark -处理客服中心按钮的点击事件 - (void)handleCallCenterClickActionWithUrl:(NSString *)url decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{ ZJLog(@"url:%@",url); if ([url containsString:@"cloudapp://goodsDetail?goodsId="]) { // 跳转商品详情 // 去掉前缀 NSArray * arr = [url componentsSeparatedByString:@"goodsId="]; NSString * goodsId = arr.lastObject; SFGoodsDetailVC * goodsDetailVc = [SFGoodsDetailVC new]; goodsDetailVc.goodsId = [goodsId integerValue]; goodsDetailVc.enterType = GoodsDetailEnterType_Normal; goodsDetailVc.souce = CommodityDetailSouce_Banner; // 首页Banner [self.navigationController pushViewController:goodsDetailVc animated:YES]; // 拦截跳转 decisionHandler(WKNavigationActionPolicyCancel); } else { // 其他 // 不拦截跳转 decisionHandler(WKNavigationActionPolicyAllow); } }
|
2.WKScriptMessageHandler
JS 调 OC 方法
- WKScriptMessageHandler 介绍
WKScriptMessageHandler
是一个 代理
,代理有一个方法:
1 2
| // 当接收到 JS 消息 时调用,是 UserContentController 委托的代理方法 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
|
上面的代理回调方法是 WKScriptMessageHandler
的代理回调方法,当接收到 JS
消息时调用,是 UserContentController(调度器)
委托的代理方法。
- WKUserContentController 介绍
WKWebView
和 JS
交互,那就得提到 WKUserContentController
,什么是 WKUserContentController
? WKUserContentController
的作用?
WKUserContentController
可以理解为 调度器
,用于 JS 和 OC 内容交互
,下面我们看一下具体有哪些方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @interface WKUserContentController : NSObject <NSCoding> // 与内容交互的脚本对象数组 @property (nonatomic, readonly, copy) NSArray<WKUserScript *> *userScripts; // 添加一个脚本,可以理解为注入一个对象 - (void)addUserScript:(WKUserScript *)userScript; // 移除所有脚本 - (void)removeAllUserScripts;
// 添加 JS 消息处理并设置代理-重点 - (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
// 根据 name 移除所注入的 scriptMessageHandler - (void)removeScriptMessageHandlerForName:(NSString *)name; @end
|
这里有一个重要的方法:addScriptMessageHandler:name
,添加 JS
消息处理并设置代理
- 示例:
1、JS
与 OC
约定好方法,如 ShowMessageFromWKWebView:
2、OC
使用 WKUserContentController
的 addScriptMessageHandler:name:
方法设置代理并接收名为 ShowMessageFromWKWebView
的消息
3、JS
通过 window.webkit.messageHandlers.name.postMessage()
的方式将方法 ShowMessageFromWKWebView
发送消息到 OC
4、OC
通过 WKScriptMessageHandler
的代理回调方法 userContentController:didReceiveScriptMessage:
中读取 name
为 ShowMessageFromWKWebView
的消息,消息数据在 message.body
中
1 2 3 4 5 6 7 8 9 10 11
| - (void)setupWKWebView{ WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init]; configuration.userContentController = [[WKUserContentController alloc] init]; // 添加 JS 消息处理并设置代理 [configuration.userContentController addScriptMessageHandler:self name:@"ShowMessageFromWKWebView"];
WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration]; webView.UIDelegate = self; [self.view addSubview:self.wkWebV]; }
|
1 2 3 4
| function showMessageFromWKWebViewClick() { // JS 发送消息到 OC window.webkit.messageHandlers.ShowMessageFromWKWebView.postMessage({title:'WKWebView', message:'测试WKWebView和OC交互'}); }
|
- 实现
WKScriptMessageHandler
代理方法,当接收到 JS
消息 name
为 ShowMessageFromWKWebView
时调用,会回调此代理方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #pragma mark - WKScriptMessageHandler - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{ // 从协议中我们可以看出这里使用了两个类 WKUserContentController 和 WKScriptMessage。WKUserContentController 可以理解为 调度器,WKScriptMessage 则是携带的数据。 // OC 读取 JS 的消息数据 NSLog(@"body:%@",message.body); if ([message.name isEqualToString:@"ShowMessageFromWKWebView"]) { NSDictionary * dict = message.body; NSString * messageStr = [dict objectForKey:@"message"]; NSString * titleStr = [dict objectForKey:@"title"]; NSLog(@"messageStr:%@",messageStr); NSLog(@"titleStr:%@",titleStr); } }
|
iOS 调用 JS
iOS
调用 JS
方法,是通过 WKWebView
的 evaluateJavaScript
,可以 传递参数
,将拼接字符串传递给 JS
,拼接的字符串有格式要求:方法名('参数')
,是 WKWebView
下的一个方法
1 2 3 4 5
| // OC传值JS的代码 NSString * returnJSStr = [NSString stringWithFormat:@"showMessageFromWKWebViewResult('%@')", @"message传到OC成功"]; [self.wkWebV evaluateJavaScript:returnJSStr completionHandler:^(id _Nullable result, NSError * _Nullable error) { NSLog(@"%@,%@",result,error); }];
|
1 2 3 4 5 6 7
| // JS 接收 OC 消息 function showMessageFromWKWebViewResult(returnStr) { if (returnStr != null) { alert("JS已经收到OC的传值"); } document.getElementById("returnTextrea").value = returnStr; }
|
KVO 监听 路由
WKWebView
也允许监听路由的方式,对属性进行监听
通过 KeyPath路由
监听 title
可以动态修改 title
1 2
| // 监听标题 [self.wkWebV zj_addObserver:self forKeyPath:@"title"];
|
通过 KeyPath路由
监听 estimatedProgress
可以做 进度条
1 2
| // 添加监听 [self.wkWebV zj_addObserver:self forKeyPath:@"estimatedProgress"];
|
通过 KeyPath路由
监听 URL
,即在跳转过程中 拦截URL
,可以去做一些 动态跳转到 OC
页面
1 2
| // 监听跳转 [self.wkWebV zj_addObserver:self forKeyPath:@"URL"];
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #pragma mark -进度的监听 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ NSString * url = self.wkWebV.URL.absoluteString; ZJLog(@"url:%@",url); if (object == self.wkWebV && [keyPath isEqualToString:@"title"]) { // 标题 if (self.wkWebV.title.length > 0) { [self.navStatusV.titleL setTitle:self.wkWebV.title titleColor:kMainTextColor font:16 isBlod:YES]; } }else if (object == self.wkWebV && [keyPath isEqualToString:@"estimatedProgress"]) { // 进度条 CGFloat newProgress = [[change objectForKey:NSKeyValueChangeNewKey] doubleValue]; // ZJLog(@"newProgress:%f",newProgress); if (newProgress <= 0.05f) { newProgress = 0.05f; } [self.progressV changeProgressValue:newProgress]; }else if (object == self.wkWebV && [keyPath isEqualToString:@"/cloudApp/customer/service"]) { // 跳转到客服中心 ZJLog(@"跳转到客服中心"); } }
|