美文网首页iOS小模块
关于js和swift交互的理解(二)

关于js和swift交互的理解(二)

作者: 4fdbb38ec548 | 来源:发表于2017-03-08 17:39 被阅读180次

    本来打算第二天就写呢,结果一直忙到了周五,想想怕忘了写,于是挤了一点时间来把剩下的给大家补上,上篇文章介绍的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颗星!!!

    相关文章

      网友评论

        本文标题:关于js和swift交互的理解(二)

        本文链接:https://www.haomeiwen.com/subject/abzbgttx.html