iOS與Javascript互動實戰
下面內容已不再在此更新,請
關注微信公眾號:iOSDevShares
加QQ群:324400294
加個人微信:huangyibiao520
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的知識是有不正確的地方,還請指出來,
你們的反饋,會是對我最大的幫助,謝謝!