iOS与Javascript交互实战

下面内容已不再在此更新,请


关注微信公众号:iOSDevShares

加QQ群:324400294

加个人微信:huangyibiao520


Swift版与JS交互实战篇:

http://mp.weixin.qq.com/s?__biz=MzIzMzA4NjA5Mw==&mid=214070747&idx=1&sn=57b45fa293d0500365d9a0a4ff74a4e1#rd


OC版与JS交互实战篇:

http://mp.weixin.qq.com/s?__biz=MzIzMzA4NjA5Mw==&mid=214063688&idx=1&sn=903258ec2d3ae431b4d9ee55cb59ed89#rd


因为项目需要做一个活动,而这个活动的信息是源于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的知识是有不正确的地方,还请指出来,

你们的反馈,会是对我最大的帮助,谢谢!

你可能感兴趣的:(ios,ios,WebView与js交互,Webview与JS,JS与Webview,js调用OC方法,js与webview交互)