Swift版與JS交互實戰篇:
OC版與JS交互實戰篇:
因爲項目需要做一個活動,而這個活動的信息是源於HTML5寫的,而這個操作網頁的過程上,
是需要到與原生APP這邊交互的,因爲這就增加了一個需求,那就是與JS交互。
目前很流行的庫有WebviewJavaScriptBridge和OVGap,這兩個庫都是讓webview與JS建立起一條橋樑,
這樣就可以相互通信了。
花了兩天的時間,反反覆覆地研究了WebviewJavaScriptBridge和OVGap這兩個庫,也在網上搜索了很多的
相關博客看,可是都沒有滿足我的需求。網上的教程幾乎都是webview給調用JS,使用系統提供的方法,這是
想學Easy就可以做到的,但是如果想讓JS調用我們的原生的方法,那就不容易了,就需要一條橋樑,在JS響應的
時候能回調OC的方法。這兩個庫都是可以滿足我們的,但是在JS端需要添加對應的JS,對於前者,還需要把響應的
方法放到橋樑內,如:
- function connectWebViewJavascriptBridge(callback) {
- if (window.WebViewJavascriptBridge) {
- callback(WebViewJavascriptBridge)
- } else {
- document.addEventListener('WebViewJavascriptBridgeReady', function() {
- callback(WebViewJavascriptBridge)
- }, false)
- }
- }
- connectWebViewJavascriptBridge(function(bridge) {
- var uniqueId = 1
- function log(message, data) {
- var log = document.getElementById('log')
- var el = document.createElement('div')
- el.className = 'logLine'
- el.innerHTML = uniqueId++ + '. ' + message + ':<br/>' + JSON.stringify(data)
- if (log.children.length) { log.insertBefore(el, log.children[0]) }
- else { log.appendChild(el) }
- }
- bridge.init(function(message, responseCallback) {
- log('JS got a message', message)
- var data = { 'Javascript Responds':'Wee!' }
- log('JS responding with', data)
- responseCallback(data)
- })
- bridge.registerHandler('testJavascriptHandler', function(data, responseCallback) {
- log('ObjC called testJavascriptHandler with', data)
- var responseData = { 'Javascript Says':'Right back atcha!' }
- log('JS responding with', responseData)
- responseCallback(responseData)
- })
- var button = document.getElementById('buttons').appendChild(document.createElement('button'))
- button.innerHTML = 'Send message to ObjC'
- button.onclick = function(e) {
- e.preventDefault()
- var data = 'Hello from JS button'
- log('JS sending message', data)
- bridge.send(data, function(responseData) {
- log('JS got response', responseData)
- })
- }
- document.body.appendChild(document.createElement('br'))
- var callbackButton = document.getElementById('buttons').appendChild(document.createElement('button'))
- callbackButton.innerHTML = 'Fire testObjcCallback'
- callbackButton.onclick = function(e) {
- e.preventDefault()
- log('JS calling handler "testObjcCallback"')
- bridge.callHandler('testObjcCallback', {'foo': 'bar'}, function(response) {
- log('JS got response', response)
- })
- }
- })
- </script>
connectWebViewJavascriptBridge
這個方法是必須的,而響應要放在這個方法中,這樣對安卓端可能會中影響,於是放棄了這個庫的使用。將下來是使用OVGap這個庫。
使用這個庫前,需要給HTML5中引入對方的腳本,叫ovgap.js,可到Github下載:
- ;(function() {
- var require, define;
- (function () {
- var modules = {},
- // Stack of moduleIds currently being built.
- requireStack = [],
- // Map of module ID -> index into requireStack of modules currently being built.
- inProgressModules = {},
- SEPERATOR = ".";
- function build(module) {
- var factory = module.factory,
- localRequire = function (id) {
- var resultantId = id;
- //Its a relative path, so lop off the last portion and add the id (minus "./")
- if (id.charAt(0) === ".") {
- resultantId = module.id.slice(0, module.id.lastIndexOf(SEPERATOR)) + SEPERATOR + id.slice(2);
- }
- return require(resultantId);
- };
- module.exports = {};
- delete module.factory;
- factory(localRequire, module.exports, module);
- return module.exports;
- }
- require = function (id) {
- if (!modules[id]) {
- throw "module " + id + " not found";
- } else if (id in inProgressModules) {
- var cycle = requireStack.slice(inProgressModules[id]).join('->') + '->' + id;
- throw "Cycle in require graph: " + cycle;
- }
- if (modules[id].factory) {
- try {
- inProgressModules[id] = requireStack.length;
- requireStack.push(id);
- return build(modules[id]);
- } finally {
- delete inProgressModules[id];
- requireStack.pop();
- }
- }
- return modules[id].exports;
- };
- define = function (id, factory) {
- if (modules[id]) {
- throw "module " + id + " already defined";
- }
- modules[id] = {
- id: id,
- factory: factory
- };
- };
- define.remove = function (id) {
- delete modules[id];
- };
- define.moduleMap = modules;
- })();
- define("ov_gap", function(require, exports, module) {
- var ovGap = {
- callbackId: Math.floor(Math.random() * 2000000000),
- callbacks: {},
- commandQueue: [],
- groupId: Math.floor(Math.random() * 300),
- groups: {},
- listeners: {},
- invoke: function(cmd, params, onSuccess, onFail) {
- if(!cmd) cmd = "defaultCommand";
- if(!params) params = {};
- this.callbackId ++;
- this.callbacks[this.callbackId] = {
- success: onSuccess,
- fail: onFail
- };
- var rurl = "ovgap://" + cmd + "/" + JSON.stringify(params) + "/" + this.callbackId;
- document.location = rurl;
- },
- dispatchCommand: function(cmd, params, onSuccess, onFail) {
- if(!cmd) cmd = "defaultCommand";
- if(!params) params = {};
- this.callbackId ++;
- this.callbacks[this.callbackId] = {
- success: onSuccess,
- fail: onFail
- };
- var command = cmd + "/" + JSON.stringify(params) + "/" + this.callbackId;
- this.commandQueue.push(command);
- },
- fetchNativeCommands: function() {
- var json = JSON.stringify(this.commandQueue);
- this.commandQueue = [];
- return json;
- },
- activate: function() {
- document.location = "ovgap://ready";
- },
- // return group ID
- createGroup: function() {
- this.groupId ++;
- this.groups[this.groupId] = [];
- return this.groupId;
- },
- dispatchCommandInGroup: function(cmd, params, onSuccess, onFail, groupId) {
- if (!this.groups[groupId]) return false;
- if(!cmd) cmd = "defaultCommand";
- if(!params) params = {};
- this.callbackId ++;
- this.callbacks[this.callbackId] = {
- success: onSuccess,
- fail: onFail
- };
- var command = cmd + "/" + JSON.stringify(params) + "/" + this.callbackId;
- this.groups[groupId].push(command);
- return true;
- },
- activateGroup: function(groupId) {
- if (!this.groups[groupId]) return false;
- document.location = "ovgap://group/" + groupId;
- },
- fetchNativeGroupCommands: function(groupId) {
- if (!this.groups[groupId]) return [];
- var json = JSON.stringify(this.groups[groupId]);
- this.groups[groupId] = [];
- return json;
- },
- callbackSuccess: function(callbackId, params) {
- try {
- ovGap.callbackFromNative(callbackId, params, true);
- } catch (e) {
- console.log("Error in error callback: " + callbackId + " = " + e);
- }
- },
- callbackError: function(callbackId, params) {
- try {
- ovGap.callbackFromNative(callbackId, params, false);
- } catch (e) {
- console.log("Error in error callback: " + callbackId + " = " + e);
- }
- },
- callbackFromNative: function(callbackId, params, isSuccess) {
- var callback = this.callbacks[callbackId];
- if (callback) {
- if (isSuccess) {
- callback.success && callback.success(callbackId, params);
- } else {
- callback.fail && callback.fail(callbackId, params);
- }
- delete ovGap.callbacks[callbackId];
- };
- },
- addGapListener: function(listenId, onSuccess, onFail) {
- if (!listenId || !onSuccess || !onFail) return;
- this.listeners[listenId] = {
- success : onSuccess,
- fail : onFail
- };
- },
- removeListener: function(listenId) {
- if (!this.listeners[listenId]) return;
- this.listeners[listenId] = null;
- },
- triggerListenerSuccess: function(listenId, params) {
- if (!this.listeners[listenId]) return;
- var listener = this.listeners[listenId];
- listener.success && listener.success(listenId, params);
- },
- triggerListenerFail: function(listenId, params) {
- if (!this.listeners[listenId]) return;
- var listener = this.listeners[listenId];
- listener.fail && listener.fail(listenId, params);
- }
- };
- module.exports = ovGap;
- });
- window.ov_gap = require("ov_gap");
- }) ();
給按鈕添加一個點擊事件,回調如下:
- function onButtonClick() {
- // 下面是我需要處理的事,處理完之後
- alert('這裏我是要處理一些事的,如果有需要的話。');
- // 這裏是回調我們前端與後端商量好的方法
- // activityList是oc中的方法
- window.ov_gap.invoke("activityList", null, success, fail);
- }
這樣就從JS回調到了IOS端的OC方法,然後處理我們想做的事。
可是這兩個庫都需要添加這些東西,做HTML5的人可不願意,因爲這樣的話,對於IOS的處理是一種,對於安卓和WP呢?又得寫一份嗎?
於是我又去尋找別的庫,有一個叫apache cordova的庫,是支持ios,android,wp的,可是太大了,又是英文的,安裝也很困難,學習成本太高,
於是看了看就放棄了,這麼大的庫,給我們帶來的可不一定是好處多於動壞處啊。
轉了一圈又回到了原生的,有一個庫叫JavaScriptCore,這個是IOS7以後纔開放的API,這可以極大的簡化了我們的需求,非常的簡單,
我們只需要注入一個方法,就可以在JS中調用此方法來跟原生的OC交互。
首先得加入庫
- #import <JavaScriptCore/JavaScriptCore.h>
JSContext這個可是關鍵。
- // webView對象
- @property (nonatomic, strong, readonly) UIWebView *webView;
- @property (nonatomic, strong, readonly) JSContext *jsContext;
下面是在webview加載完成後, 關聯JS與OC:
- - (void)webViewDidFinishLoad:(UIWebView *)webView {
- [_activityView stopAnimating];
- [self dismiss];
- if (_jsContext == nil) {
- // 1.
- _jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
- // 2. 關聯打印異常
- _jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
- context.exception = exceptionValue;
- DDLogVerbose(@"異常信息:%@", exceptionValue);
- };
- _jsContext[@"activityList"] = ^(NSDictionary *param) {
- DDLogVerbose(@"%@", param);
- };
- // Mozilla/5.0 (iPhone; CPU iPhone OS 10_10 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12B411
- id userAgent = [webView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"];
- DDLogVerbose(@"%@", userAgent);
- }
- }
上面
activityList是商定好的方法名稱,在JS中寫法:
- <input type="button" value="測試log" onclick="activityList({'tytyty':'hehe'})" />
那麼經過這兩天的摸索,學習到了很多的知識。
寫DEMO的過程中,由於 後臺並沒有提供好HTML5頁面的交互來測試,需要自己寫,
我這裏是使用apache服務器,在本地創建一個HTML5頁面,自己寫一些JS來測試的,
如果大家不知道怎麼寫JS,其實是很簡單的,上w3cschool看一看,就明白了,so easy!!!!
之所以要寫下這篇文章,是因爲我發現在網上搜索出來的文章中,都是相互複製的,看來看去都是
一樣的東西而且還都是OC調JS的代碼,實在是不快。
寫下的點滴,希望對大家有用。
另外,也許我上面所講的一些關於OVGap和WebviewJavaScriptBridge的知識是有不正確的地方,還請指出來,
你們的反饋,會是對我最大的幫助,謝謝!