iOS与Javascript交互实战

因为项目需要做一个活动,而这个活动的信息是源于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 + ':' + 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)

})

}

})

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");

}) ();

给按钮添加一个点击事件,回调如下:

[javascript] view plain copy print?在CODE上查看代码片派生到我的代码片

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交互。首先得加入库[javascript] view plain copy print?在CODE上查看代码片派生到我的代码片#importJSContext这个可是关键。

[objc] view plain copy print?在CODE上查看代码片派生到我的代码片

// 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中写法:[javascript] view plain copy print?在CODE上查看代码片派生到我的代码片在点击的时候,直接回调是可以的。那么经过这两天的摸索,学习到了很多的知识。写DEMO的过程中,由于 后台并没有提供好HTML5页面的交互来测试,需要自己写,我这里是使用apache服务器,在本地创建一个HTML5页面,自己写一些JS来测试的,如果大家不知道怎么写JS,其实是很简单的,上w3cschool看一看,就明白了,so easy!!!!之所以要写下这篇文章,是因为我发现在网上搜索出来的文章中,都是相互复制的,看来看去都是一样的东西而且还都是OC调JS的代码,实在是不快。写下的点滴,希望对大家有用。另外,也许我上面所讲的一些关于OVGap和WebviewJavaScriptBridge的知识是有不正确的地方,还请指出来,你们的反馈,会是对我最大的帮助,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,924评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,781评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,813评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,264评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,273评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,383评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,800评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,482评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,673评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,497评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,545评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,240评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,802评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,866评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,101评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,673评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,245评论 2 341

推荐阅读更多精彩内容

  • 本教程中所涉及到的几种类型: JSContext, JSContext是代表JS的执行环境,通过-evaluate...
    贝勒老爷阅读 847评论 0 5
  • 单例模式 适用场景:可能会在场景中使用到对象,但只有一个实例,加载时并不主动创建,需要时才创建 最常见的单例模式,...
    Obeing阅读 2,047评论 1 10
  • body{-moz-osx-font-smoothing:grayscale;-webkit-font-smoot...
    wangyw阅读 572评论 0 0
  • 在地球上,先有石头,后有人类,或者说没有石头就没有人类。考古学对早期人类历史分期的第一个时代就是石器时代,从出现人...
    棉花兔阅读 270评论 0 2
  • 这篇文章简单的描述UICollectionView的基本使用方法,供需要简单瀑布流布局的同学查看。 UIColle...
    阿猿阅读 1,118评论 0 4