OC学习21:hybrid

张建 lol

前言

iOS 开发中,iOSJS 交互是每个程序猿必须掌握的技能。iOS8 以后,苹果推出了新框架 WebKit,使用 WKWebView 替代 UIWebView稳定性好、占用内存少,速度更快

说道 iOSJS 交互,就不得不提 Hybrid(Hybrid Mobile App),即通过 Web 网络技术与 Native 相结合的混合移动应用开发

WKWebView 特性
1、在性能、稳定性、功能方面有很大提升,直观体现是内存占用变少;
2、高达60fps的滚动刷新率以及内置手势
3、支持了更多的HTML5特性;

本文主要介绍 WKWebViewJS 交互

WKWebView 和 JS 交互的方法

  1. 拦截 URL
  2. WKScriptMessageHandler
  3. WebViewJavascriptBridge等其他第三方框架

下面以实际功能为例讲解其使用

1.拦截 URL 实现自定义跳转功能

1、和后端协定好 协议
2、通过 WKWebViewWKNavigationDelegate 代理回调 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 方法

  1. WKScriptMessageHandler 介绍
  • WKScriptMessageHandler 是一个 代理 ,代理有一个方法:
1
2
// 当接收到 JS 消息 时调用,是 UserContentController 委托的代理方法
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;

上面的代理回调方法是 WKScriptMessageHandler 的代理回调方法,当接收到 JS 消息时调用,是 UserContentController(调度器) 委托的代理方法。

  1. WKUserContentController 介绍

WKWebViewJS 交互,那就得提到 WKUserContentController ,什么是 WKUserContentControllerWKUserContentController 的作用?

  • 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. 示例:

1、JSOC 约定好方法,如 ShowMessageFromWKWebView:
2、OC 使用 WKUserContentControlleraddScriptMessageHandler:name: 方法设置代理并接收名为 ShowMessageFromWKWebView 的消息
3、JS 通过 window.webkit.messageHandlers.name.postMessage() 的方式将方法 ShowMessageFromWKWebView 发送消息到 OC
4、OC 通过 WKScriptMessageHandler 的代理回调方法 userContentController:didReceiveScriptMessage: 中读取 nameShowMessageFromWKWebView 的消息,消息数据在 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];
}
  • h5中要实现的代码
1
2
3
4
function showMessageFromWKWebViewClick() {
// JS 发送消息到 OC
window.webkit.messageHandlers.ShowMessageFromWKWebView.postMessage({title:'WKWebView', message:'测试WKWebView和OC交互'});
}
  • 实现 WKScriptMessageHandler 代理方法,当接收到 JS 消息 nameShowMessageFromWKWebView 时调用,会回调此代理方法:
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 方法,是通过 WKWebViewevaluateJavaScript,可以 传递参数,将拼接字符串传递给 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);
}];
  • JS 接收 OC 消息
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 也允许监听路由的方式,对属性进行监听

  • 监听标题 title

通过 KeyPath路由 监听 title 可以动态修改 title

1
2
// 监听标题
[self.wkWebV zj_addObserver:self forKeyPath:@"title"];
  • 监听进度 estimatedProgress

通过 KeyPath路由 监听 estimatedProgress 可以做 进度条

1
2
// 添加监听
[self.wkWebV zj_addObserver:self forKeyPath:@"estimatedProgress"];
  • 监听 URL

通过 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(@"跳转到客服中心");
}
}
  • Post title:OC学习21:hybrid
  • Post author:张建
  • Create time:2023-03-16 16:34:42
  • Post link:https://redefine.ohevan.com/2023/03/16/OC/OC学习21:hybrid/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.