Web 页面中的 JS 与 iOS Native 如何交互?JS 和 iOS Native 就好比两块没有交集的大陆,如果想要使它们相互通信就必须要建立一座“桥梁”。

WebViewJavascriptBridge 是盛名已久的 JSBridge 库,它仅使用了少量代码就实现了对于 Mac OS X 的 WebView 以及 iOS 平台的 UIWebView 和 WKWebView 三种组件的完美支持。

WebViewJavascriptBridge 主要是作为 Mac OS X 和 iOS 端(Native 端)与 JS 端相互通信,互相调用的桥梁。对于 Mac OS X 和 iOS 两种平台包含的三种 WebView 功能组件而言,WebViewJavascriptBridge 做了隐性适配,即仅用一套代码即可绑定不同平台的 WebView 组件实现同样功能的 JS 通信功能。 WebViewJavascriptBridge 对于 JS 端和 Native 端设计了对等的接口,不论是 JS 端还是 Native 端,注册本端的响应处理都是用 registerHandler 接口,调用另一端(给另一端发消息)都是用 callHandler 接口。

  1. UIWebView 使用 javaScriptCore.
  2. WKWebView 使用 WKUserContentController.

UIWebView 原生的交互原理
通过一个 JSContext 获取 UIWebView 的 JS 执行上下文。
然后通过这个上下文,进行 OC & JS 的双端交互。

 _jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];_jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception) {NSLog(@"%@",@"获取 WebView JS 执行环境失败了!");};

WKWebView 原生交互原理

通过 userContentController 把需要观察的 JS 执行函数注册起来。
然后通过一个协议方法,将所有注册过的 JS 函数执行的参数传递到此协议方法中。

注册 需要 观察的 JS 执行函数

 [webView.configuration.userContentController addScriptMessageHandler:self name:@"jsFunc"];

在 JS 中调用这个函数并传递参数数据

window.webkit.messageHandlers.jsFunc.postMessage({name : "李四",age : 22});

OC 中遵守 WKScriptMessageHandler 协议。

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message

此协议方法里的 WKScriptMessage 有 name & body 两个属性。 name 可以用来判断是哪个 JSFunc 调用了。body 则是 JSFunc 传递到 OC 的参数。

WebViewJavaScriptBridge

WebViewJavaScriptBridge 用于 WKWebView & UIWebView 中 OC 和 JS 交互。
它的基本原理是:

把 OC 的方法注册到桥梁中,让 JS 去调用。
把 JS 的方法注册在桥梁中,让 OC 去调用。

WebViewJavaScriptBridge 使用的基本步骤

  1. 首先在项目中导入 WebViewJavaScriptBridge 框架
pod ‘WebViewJavascriptBridge’
  1. 导入头文件 #import <WebViewJavascriptBridge.h>
  2. 建立 WebViewJavaScriptBridge 和 WebView 之间的关系。
_jsBridge = [WebViewJavascriptBridge bridgeForWebView:_webView];
  1. 在HTML 文件中,复制粘贴这两段 JS 函数。
function setupWebViewJavascriptBridge(callback) {if (window.WebViewJavascriptBridge) {return callback(window.WebViewJavascriptBridge)}if (window.WVJBCallbacks) {return window.WVJBCallbacks.push(callback)}window.WVJBCallbacks = [callback] // 创建一个 WVJBCallbacks 全局属性数组,并将 callback 插入到数组中。var WVJBIframe = document.createElement('iframe') // 创建一个 iframe 元素WVJBIframe.style.display = 'none' // 不显示WVJBIframe.src = 'https://__bridge_loaded__' // 设置 iframe 的 src 属性document.documentElement.appendChild(WVJBIframe) // 把 iframe 添加到当前文导航上。setTimeout(function() {document.documentElement.removeChild(WVJBIframe)}, 0)}// 这里主要是注册 OC 将要调用的 JS 方法。setupWebViewJavascriptBridge(function(bridge){});

到此为止,基本的准备工作就做完了。现在需要往桥梁中注入 OC 方法 和 JS 函数了。

往桥梁中注入 OC 方法 和 JS 函数

1、往桥梁中注入 OC 方法。

 [_jsBridge registerHandler:@"scanClick" handler:^(id data, WVJBResponseCallback responseCallback) {NSLog(@"dataFrom JS : %@",data[@"data"]);responseCallback(@"扫描结果 : www.baidu.com");}];

这段代码的意思:

  1. scanClick 是 OC block 的一个别名。
  2. block 本身,是 JS 通过某种方式调用到 scanClick 的时候,执行的代码块。
  3. data ,由于 OC 这端由 JS 调用,所以 data 是 JS 端传递过来的数据。
  4. responseCallback OC 端的 block 执行完毕之后,往 JS 端传递的数据。

2、往桥梁中注入 JS 函数.
在 JS 的方法如何注入到桥梁呢?需要在第二段 JS 代码中,注入 JS 的函数。

// 这里主要是注册 OC 将要调用的 JS 方法。setupWebViewJavascriptBridge(function(bridge){// 声明 OC 需要调用的 JS 方法。bridge.registerHanlder('testJavaScriptFunction',function(data,responseCallback){// data 是 OC 传递过来的数据.// responseCallback 是 JS 调用完毕之后传递给 OC 的数据alert("JS 被 OC 调用了.");responseCallback({data: "js 的数据",from : "JS"});})});

这段代码的意思:

  1. testJavaScriptFunction 是注入到桥梁中 JS 函数的别名。以供 OC 端调用。
  2. 回调函数的 data。 既然 JS 函数由 OC 调用,所以 data 是 OC 端传递过来的数据。
  3. responseCallback 。 JS 调用在被 OC 调用完毕之后,向 OC 端传递的数据。

基本就是:

OC 端注册 OC 的方法,OC 端调用 JS 的函数。
JS 端注册 JS 的函数,JS 端调用 OC 的方法。

场景

JS -> OC 的交互

在 HTML 中,有个按钮,点击这个按钮,修改 NavigationBar 的颜色。

  1. 在 OC 端,往桥梁注入一个修改 NavigationBar 颜色的 block.
  2. 在 JS 端,调用这个 block,来间接的达到修改颜色的目的。

首先,在 OC 中,通过 WebViewJavascriptBridge 注册一个修改 navigationBar 颜色的 Block。

[_jsBridge registerHandler:@"colorClick" handler:^(id data, WVJBResponseCallback responseCallback) {self.navigationController.navigationBar.barTintColor = [UIColor colorWithRed:arc4random_uniform(256) / 255.0 green:arc4random_uniform(256) / 255.0 blue:arc4random_uniform(256) / 255.0 alpha:1.0];responseCallback(@"颜色修改完毕!");}];

然后再 JS 中,通过某种方式去调用这个 OC 的 block。

WebViewJavascriptBridge.callHandler('colorClick',function(dataFromOC) {alert("JS 调用了 OC 注册的 colorClick 方法");document.getElementById("returnValue").value = dataFromOC;})

这里通过某种方式就是使用 WebViewJavascriptBridge.callHandler('OC 中block 别名',callback)的方式来调用。

OC -> JS 的交互

OC 上有一个UIButton,点击这儿按钮,把 HTML body 的颜色修改成橙色。

首先,往桥梁中,注入一个修改 HTML body 颜色的 JSFunction。

// 在这里声明 OC 需要主动调用 JS 的方法。setupWebViewJavascriptBridge(function(bridge) {bridge.registerHandler('changeBGColor',function(data,responseCallback){// alert('aaaaaa');document.body.style.backgroundColor = "orange";document.getElementById("returnValue").value = data;});}); 

然后在 OC 端通过桥梁调用这个 changeBGColor

 [_jsBridge callHandler:@"changeBGColor" data:@"把 HTML 的背景颜色改成橙色!!!!"];

执行效果:


 补充

OC 调用 JS 的三种情况。

    // 单纯的调用 JSFunction,不往 JS 传递参数,也不需要 JSFunction 的返回值。[_jsBridge callHandler:@"changeBGColor"];// 调用 JSFunction,并向 JS 传递参数,但不需要 JSFunciton 的返回值。[_jsBridge callHandler:@"changeBGColor" data:@"把 HTML 的背景颜色改成橙色!!!!"];// 调用 JSFunction ,并向 JS 传递参数,也需要 JSFunction 的返回值。[_jsBridge callHandler:@"changeBGColor" data:@"传递给 JS 的参数" responseCallback:^(id responseData) {NSLog(@"JS 的返回值: %@",responseData);}];

JS 调用 OC 的三种情况。

// JS 单纯的调用 OC 的 block
WebViewJavascriptBridge.callHandler('scanClick');// JS 调用 OC 的 block,并传递 JS 参数
WebViewJavascriptBridge.callHandler('scanClick',"JS 参数");// JS 调用 OC 的 block,传递 JS 参数,并接受 OC 的返回值。
WebViewJavascriptBridge.callHandler('scanClick',{data : "这是 JS 传递到 OC 的扫描数据"},function(dataFromOC){alert("JS 调用了 OC 的扫描方法!");document.getElementById("returnValue").value = dataFromOC;});

可以根据实际情况,选择合适的方法。

关于在 OC 中,往桥梁中注入 block 的注意点。

在当前控制器消失的时候,要记得把注入到桥梁中的 OC block,从桥梁中删除。
否则,可能会出现控制器无法释放的情况。

- (void)viewDidDisappear:(BOOL)animated {[super viewDidDisappear:animated];[_jsBridge removeHandler:@"scanClick"];[_jsBridge removeHandler:@"colorClick"];[_jsBridge removeHandler:@"locationClick"];[_jsBridge removeHandler:@"shareClick"];[_jsBridge removeHandler:@"payClick"];[_jsBridge removeHandler:@"goBackClick"];
}

Android

以上说的都是ios交互,安卓有一点点不同。

function connectWebViewJavascriptBridge (callback) { if (window.WebViewJavascriptBridge) {callback(WebViewJavascriptBridge)} else {document.addEventListener('WebViewJavascriptBridgeReady', function() {callback(WebViewJavascriptBridge)},false);}
}//和ios一样
connectWebViewJavascriptBridge (function(bridge) {//注册一个方法(方法名是“JS Echo”),客户端进行调用(方法名也是“JS Echo”),responseCallback是回调函数bridge.registerHandler('JS Echo', function(data, responseCallback) {console.log("JS Echo called with:", data)responseCallback(data)})//客户端已经注册好一个名为“ObjC Echo”的方法,H5直接进行调用(方法名也为“ObjC Echo”)就行,调用的时候可以传客户端需要的参数bridge.callHandler('ObjC Echo', {'key':'value'}, function responseCallback(responseData) {console.log("JS received response:", responseData)})
})

封装:

/* eslint-disable */function setAndroid () {var bridge = {default: this,callHandler: function(b, a, c) {var e = '''function' == typeof a && ((c = a), (a = {}))a = { data: void 0 === a ? null : a }if ('function' == typeof c) {var g = 'dscb' + window.dscb++window[g] = ca._dscbstub = g}a = JSON.stringify(a)if (window._dsbridge) e = _dsbridge.callHandler(b, a)else if (window._dswk ||-1 != navigator.userAgent.indexOf('_dsbridge'))e = prompt('_dsbridge=' + b, a)return JSON.parse(e || '{}').data},register: function(b, a, c) {c = c ? window._dsaf : window._dsfwindow._dsInit ||((window._dsInit = !0),setTimeout(function() {bridge.callHandler('_dsb.dsinit')}, 0))'object' == typeof a ? (c._obs[b] = a) : (c[b] = a)},registerAsyn: function(b, a) {this.register(b, a, !0)},hasNativeMethod: function(b, a) {return this.callHandler('_dsb.hasNativeMethod', {name: b,type: a || 'all'})},disableJavascriptDialogBlock: function(b) {this.call('_dsb.disableJavascriptDialogBlock', {disable: !1 !== b})}}!(function() {if (!window._dsf) {var b = {_dsf: { _obs: {} },_dsaf: { _obs: {} },dscb: 0,WebViewJavascriptBridge: bridge,close: function() {bridge.callHandler('_dsb.closePage')},_handleMessageFromNative: function(a) {var e = JSON.parse(a.data),b = { id: a.callbackId, complete: !0 },c = this._dsf[a.method],d = this._dsaf[a.method],h = function(a, c) {b.data = a.apply(c, e)bridge.call('_dsb.returnValue', b)},k = function(a, c) {e.push(function(a, c) {b.data = ab.complete = !1 !== cbridge.callHandler('_dsb.returnValue', b)})a.apply(c, e)}if (c) h(c, this._dsf)else if (d) k(d, this._dsaf)else if (((c = a.method.split('.')), !(2 > c.length))) {a = c.pop()var c = c.join('.'),d = this._dsf._obs,d = d[c] || {},f = d[a]f && 'function' == typeof f? h(f, d): ((d = this._dsaf._obs),(d = d[c] || {}),(f = d[a]) &&'function' == typeof f &&k(f, d))}}},afor (a in b) window[a] = b[a]bridge.register('_hasJavascriptMethod', function(a, b) {b = a.split('.')if (2 > b.length) return !(!_dsf[b] && !_dsaf[b])a = b.pop()b = b.join('.')return (b = _dsf._obs[b] || _dsaf._obs[b]) && !!b[a]})}})();return bridge
}const jsBridge = {init: function() {if (/(Android)/i.test(navigator.userAgent.toLowerCase())) {// setAndroid()this.register = setAndroid().register}},connectJsBridge: function(callback) {if (window.WebViewJavascriptBridge) {return callback(window.WebViewJavascriptBridge)}if (window.WVJBCallbacks) {return window.WVJBCallbacks.push(callback)}window.WVJBCallbacks = [callback]var WVJBIframe = document.createElement('iframe')WVJBIframe.style.display = 'none'WVJBIframe.src = 'https://__bridge_loaded__'document.documentElement.appendChild(WVJBIframe)setTimeout(function() {document.documentElement.removeChild(WVJBIframe)}, 0)},addBridgeProperty: function(bridge) {const dependencies = ['getUserInfo', 'login', 'getLocation', 'share',..... ];try {dependencies.forEach(dependency => {// if (!this[dependency]) {Object.defineProperty(this, dependency, {configurable: true,  // 注:允许重复定义属性(移除会造成重新定义报错)get: () => {return bridge.callHandler.bind(bridge, dependency)}})// }})} catch (error) {console.error(error)}// if (this.openWebPage) {this.openWebPage = url => {window.location.href = `app内嵌的域名?url=${encodeURIComponent(url)}`}// }return this},ready: function(callback) {this.connectJsBridge(bridge => {callback(this.addBridgeProperty(bridge))})},register: function(...args) {window.WebViewJavascriptBridge.registerHandler(...args)},
}jsBridge.init()
export default jsBridge//使用:
import JSBridge from './base/jsBridge'JSBridge.ready((bridge: any) => {bridge.getUserInfo((json: string | object) => {console.log(json)})
})

WebViewJavascriptBridge相关推荐

  1. WebViewJavascriptBridge的简单使用

    - (void)viewDidLoad { UIWebView* webView = [[UIWebView alloc] initWithFrame:self.view.bounds]; webVi ...

  2. iOS WebviewJavascriptBridge 源码研读笔记

    这两天接近元旦,事情稍微少些,有些时间,索性写点什么,就从最擅长的iOS混合开发写起了,由于iOS开发经验不到四年吧,期间还搞了一年半的前端,有些知识可能还是积累的不足,能力不足,水平有限,可能有谬误 ...

  3. iOS原生与html交互 使用第三方WebViewJavascriptBridge

    HTML页面代码 <!DOCTYPE html> <html xmlns:http="http://www.w3.org/1999/xhtml"> < ...

  4. iOS下JS与OC互相调用(五)--UIWebView + WebViewJavascriptBridge

    WebViewJavascriptBridge是一个有点年代的JS与OC交互的库,使用该库的著名应用还挺多的,目前这个库有7000+star.我去翻看了它的第一版本已经是4年前了,在版本V4.1.4以 ...

  5. WebViewJavascriptBridge原理解析

    基本说明 我们的项目是一个OC与javascript重度交互的app,OC与javascript交互的那部分是在WebViewJavascriptBridge的github地址的基础上修改的,WebV ...

  6. 通过WebViewJavascriptBridge实现OC与JS交互

    这里照搬Github的Demo,其实还是很易懂的,首先,要在控制器的.h文件当中实现浏览器控件的协议: 1 #import <UIKit/UIKit.h> 2 3 @interface E ...

  7. web与APP之间的交互---WebViewJavascriptBridge

    在实际项目之中,经常会遇到app之中嵌入网页的情况(Hybrid),就需要web网页与原生app之间交互,比如获取当前用户信息等.一种简单的方式就是通过url参数来搞定,但是这种方式异常死板,所以有了 ...

  8. (0005) iOS 开发之WebViewJavascriptBridge的升级问题

    注意:这里讲的是升级:不会用的先去学习怎么使用. 为什么我想起来升级尼?(使用的4.1.4-5.0.5) 问题:使用的WebViewJavascriptBridge4.1.4的使用,iOS 10上面释 ...

  9. iPhone 和 iPad的ios 开发中 利用 WebViewJavascriptBridge组件,通过 UIWebView 对Html进行双向通讯...

    本文转载至 http://blog.csdn.net/remote_roamer/article/details/7261490 WebViewJavascriptBridge 项目的 官网 http ...

  10. iOS下JS与OC互相调用(六)--WKWebView + WebViewJavascriptBridge

    2019独角兽企业重金招聘Python工程师标准>>> iOS下JS与OC互相调用(六)--WKWebView + WebViewJavascriptBridge 转载:原地址 ht ...

最新文章

  1. html怎么把图片作为背景_抖音背景图片怎么弄,抖音背景图片引导关注
  2. centos7全离线安装redis3.2.8集群
  3. pytest使用简介
  4. 皮一皮:同一样物件,不一样的时间...
  5. Java四大知识点讲解,初学者必看
  6. cat全链路监控_谛听全链路监控平台实践与思考
  7. python对话机器人框架_长篇文讲解:使用Python AIML搭建聊天机器人的方法示例(收藏)...
  8. 谷歌不支持调用摄像头麦克风_谷歌发布安卓11系统:全新界面、更严的隐私管理...
  9. 用ExtJs+Linq+Wcf打造简单grid
  10. 进程占用过高cpu的排查
  11. 技术和技术管理人员评价标准
  12. linux档案内容怎么写,Linux cat输出档案命令详解
  13. unityui计分_铅计分成长
  14. Mac Air 配置Android开发环境
  15. RS232串口线连接方法
  16. sap销售发货的流程_现金及快速销售流程
  17. [MySQL远程备份策略举例]
  18. Bootstrap-03 (前台开发框架)
  19. GPRS网络组成及接口
  20. 从优步僵尸车事件看源码保护的必要性

热门文章

  1. 【C++学习笔记】类型转换和跳转语句
  2. 思维导图的优缺点与绘制思维导图方法
  3. 从UIL库谈Android图片加载中需要注意的事情
  4. ios Symbol(s) not found for architecture arm64
  5. 笔记:《高效能人士的七个习惯》第九章 习惯六 统合综效——创造性合作的原则
  6. 人工智能是当前最好的计算机研究方向吗?
  7. 微信小程序 使用map组件 地图获取位置、移动选点、逆地址解析
  8. android pppd流程,pppd调试心得.md
  9. adb关闭系统自动更新
  10. length与length()的区别