本来打算第二天就写呢,结果一直忙到了周五,想想怕忘了写,于是挤了一点时间来把剩下的给大家补上,上篇文章介绍的swift调用js(文章地址 ),这篇文章介绍js调用swift
1.JS调用OC:webView拦截链接的方法
此方法本人并没有测试,是直接copy过来的,因为感觉此方法不是很好
-(BOOL)webView:(UIWebView *)webView
shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType;
实现以上webView的代理方法,当webView每次开始加载URL时会进入这个方法,我们便可以在这个方法实现JS调用OC。
JS代码如下:
OC代码如下:
如上图当JS中window.location.href = "iOS:shareToTest"的代码被触发,会进入OC中的这个代理方法,并且获得"iOS:shareToTest"这个字符串,接下进行一系列的字符串解释,得到需要被实现的方法名且调用。如果需要传值可把需要传的值拼接在字符串上,字符串解释后获取响应的值后调用一下方法:
这种JS调用OC的方法的缺点十分明显,需要繁琐地解释字符串得到相应的方法名和传值,且最多只能有两个值,调用的方法也不能传递返回值;但是也有一个优点:不需要等待页面加载完才触发,当相应的代码被运行就能调用OC的方法,这也是下面要讲到的JavaScriptCore的一个小坑。
2.苹果推荐的框架--JavaScriptCore
这种方法是利用iOS7后新出的框架实现的,跟我上文swift调用js 第二个方法是配套使用的,下面上代码:
首先创建一个类JSObjCModel和JavaScriptSwiftDelegate,代理里面写的是js可以调用的方法,JSObjCModel这个名字需要跟前端的小伙伴一起约定好的,js里面也是要用的
import UIKit
import JavaScriptCore
// All methods that should apply in Javascript, should be in the following protocol.
@objc protocol JavaScriptSwiftDelegate: JSExport {
func callSystemCamera();
func showAlert(_ title: String, msg: String);
func callWithDict(_ dict: [String: AnyObject])
func jsCallObjcAndObjcCallJsWithDict(_ dict: [String: AnyObject]);
}
class JSObjCModel: NSObject,JavaScriptSwiftDelegate {
weak var controller: UIViewController?
weak var jsContext: JSContext?
func goGroup(_ commonId: String) {
print(commonId)
}
func callSystemCamera() {
print("js call objc method: callSystemCamera");
let jsFunc = self.jsContext?.objectForKeyedSubscript("jsFunc");
print(jsFunc?.toString()!)
jsFunc?.call(withArguments: []);
}
func showAlert(_ title: String, msg: String) {
DispatchQueue.main.async { () -> Void in
let alert = UIAlertController(title: title, message: msg, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "ok", style: .default, handler: nil))
self.controller?.present(alert, animated: true, completion: nil)
}
}
// JS调用了我们的方法
func callWithDict(_ dict: [String : AnyObject]) {
print("js call objc method: callWithDict, args: %@", dict)
}
// JS调用了我们的就去
func jsCallObjcAndObjcCallJsWithDict(_ dict: [String : AnyObject]) {
print("js call objc method: jsCallObjcAndObjcCallJsWithDict, args: %@", dict)
let jsParamFunc = self.jsContext?.objectForKeyedSubscript("jsParamFunc");
let dict = NSDictionary(dictionary: ["age": 18, "height": 168, "name": "lili"])
jsParamFunc?.call(withArguments: [dict])
}
}
然后就需要在webViewDidFinishLoad把刚刚创建的那个类注入到js里面,那么js就可以通过这个类去调用swift里的方法了
func webViewDidFinishLoad(_ webView: UIWebView) {
hideActivity()
//删除头部试图
let header = "document.getElementById('header').remove()"
webView.stringByEvaluatingJavaScript(from: header)
self.title = webView.stringByEvaluatingJavaScript(from: "document.title")
let context = webView.value(forKeyPath: "documentView.webView.mainFrame.javaScriptContext") as? JSContext
let model = JSObjCModel()
model.controller = self
model.jsContext = context
self.jsContext = context
// 这一步是将OCModel这个模型注入到JS中,在JS就可以通过OCModel调用我们公暴露的方法了。
self.jsContext?.setObject(model, forKeyedSubscript: "OCModel" as (NSCopying & NSObjectProtocol)!)
self.jsContext?.exceptionHandler = {
(context, exception) in
print("exception @", exception!)
}
}
第三部js里面的写法是(如图,奇怪的是为何不能复制了,直接截图了),这个OCModel必须跟你的小伙伴商量好才可以,注意你在webViewDidFinishLoad里注入模型的时候写的必须一致才行
但是注意这个框架最需要强调的一点是:JS调用OC时,是需要等浏览器加载完页面后才能进行交互(相当坑、很坑!!!),这个是需要看需求的,如果你需要在网页加载的时候就调用,就放弃这个方吧,继续看下面的第三种办法
这个方法边写边发现了问题,问题如下调用2个参数时,怎么调用都不成功,如下图所有的地方都没错:
而js里的调用方法就是写的
大概经过半天的测试和调试,我终于发现了问题所在:
这就是咱们基础知识不扎实的地方了,还记得swift里的方法名是怎么定义的吗???
js里的方法应该写成什么就对了呢?
经过本大神的认真审查js里应该这么写navigateToCreateGroupBuyDataString才可以调到swift的方法,怎么样是不是想起了什么
3.优秀的第三方框架--WebViewJavascriptBridge
由于我利用第二个就解决了需求,但是我还是感觉第三种方法最好,目前这个库还在更新中,我也没有用我的swift项目中,但是目测这是最好的解决方法,写到这里我还是忍不住,相对它尝试一下
先奉上这个框架的GitHub地址WebViewJavascriptBridge
具体用法在GitHub上说的挺详细的,下面大概说一下吧:
1) 首先把第三方加入你的项目并引用文件
#import"WebViewJavascriptBridge.h"
...
@property WebViewJavascriptBridge* bridge;
2) 注册一个WebViewJavascriptBridge的对象 可以用 WKWebView, UIWebView (iOS) or WebView (OSX):
self.bridge = [WebViewJavascriptBridgebridgeForWebView:webView];
3) oc里面注册一个Handler和发送一个call(图解)
handler注册
[self.bridgeregisterHandler:@"ObjC Echo"handler:^(iddata, WVJBResponseCallback responseCallback) {NSLog(@"ObjC Echo called with:%@", data);responseCallback(data);}];
发送call
[self.bridgecallHandler:@"JS Echo"data:nilresponseCallback:^(idresponseData) {NSLog(@"ObjC received response:%@", responseData);}];
4) 把下述代码复制到JS
functionsetupWebViewJavascriptBridge(callback) {if(window.WebViewJavascriptBridge) {returncallback(WebViewJavascriptBridge); }if(window.WVJBCallbacks) {returnwindow.WVJBCallbacks.push(callback); }window.WVJBCallbacks=[callback];varWVJBIframe=document.createElement('iframe');WVJBIframe.style.display='none';WVJBIframe.src='https://__bridge_loaded__';document.documentElement.appendChild(WVJBIframe);setTimeout(function() {document.documentElement.removeChild(WVJBIframe) },0)}
5)js里面写的方法
setupWebViewJavascriptBridge(function(bridge) {/*Initialize your app here*/bridge.registerHandler('JS Echo',function(data,responseCallback) {console.log("JS Echo called with:", data)responseCallback(data) })bridge.callHandler('ObjC Echo', {'key':'value'},functionresponseCallback(responseData) {console.log("JS received response:", responseData) })})
如果真的要用到这个框架,除了iOS的开发人员外,也要让后台的人了解这个框架,并在合适的位置注入上述JS代码,虽然是比较麻烦,但是这个框架确实挺好用,推荐指数5颗星!!!