本來打算第二天就寫呢,結(jié)果一直忙到了周五,想想怕忘了寫,于是擠了一點時間來把剩下的給大家補上,上篇文章介紹的swift調(diào)用js(文章地址?),這篇文章介紹js調(diào)用swift
1.JS調(diào)用OC:webView攔截鏈接的方法
此方法本人并沒有測試,是直接copy過來的,因為感覺此方法不是很好
-(BOOL)webView:(UIWebView *)webView
shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType;
實現(xiàn)以上webView的代理方法,當webView每次開始加載URL時會進入這個方法,我們便可以在這個方法實現(xiàn)JS調(diào)用OC。
JS代碼如下:
OC代碼如下:
如上圖當JS中window.location.href = "iOS:shareToTest"的代碼被觸發(fā),會進入OC中的這個代理方法,并且獲得"iOS:shareToTest"這個字符串,接下進行一系列的字符串解釋,得到需要被實現(xiàn)的方法名且調(diào)用。如果需要傳值可把需要傳的值拼接在字符串上,字符串解釋后獲取響應(yīng)的值后調(diào)用一下方法:
這種JS調(diào)用OC的方法的缺點十分明顯,需要繁瑣地解釋字符串得到相應(yīng)的方法名和傳值,且最多只能有兩個值,調(diào)用的方法也不能傳遞返回值;但是也有一個優(yōu)點:不需要等待頁面加載完才觸發(fā),當相應(yīng)的代碼被運行就能調(diào)用OC的方法,這也是下面要講到的JavaScriptCore的一個小坑。
2.蘋果推薦的框架--JavaScriptCore
這種方法是利用iOS7后新出的框架實現(xiàn)的,跟我上文swift調(diào)用js 第二個方法是配套使用的,下面上代碼:
首先創(chuàng)建一個類JSObjCModel和JavaScriptSwiftDelegate,代理里面寫的是js可以調(diào)用的方法,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調(diào)用了我們的方法
func callWithDict(_ dict: [String : AnyObject]) {
print("js call objc method: callWithDict, args: %@", dict)
}
// JS調(diào)用了我們的就去
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把剛剛創(chuàng)建的那個類注入到j(luò)s里面,那么js就可以通過這個類去調(diào)用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調(diào)用我們公暴露的方法了。
self.jsContext?.setObject(model, forKeyedSubscript: "OCModel" as (NSCopying & NSObjectProtocol)!)
self.jsContext?.exceptionHandler = {
(context, exception) in
print("exception @", exception!)
}
}
第三部js里面的寫法是(如圖,奇怪的是為何不能復(fù)制了,直接截圖了),這個OCModel必須跟你的小伙伴商量好才可以,注意你在webViewDidFinishLoad里注入模型的時候?qū)懙谋仨氁恢虏判?/p>
但是注意這個框架最需要強調(diào)的一點是:JS調(diào)用OC時,是需要等瀏覽器加載完頁面后才能進行交互(相當坑、很坑!!!),這個是需要看需求的,如果你需要在網(wǎng)頁加載的時候就調(diào)用,就放棄這個方吧,繼續(xù)看下面的第三種辦法
這個方法邊寫邊發(fā)現(xiàn)了問題,問題如下調(diào)用2個參數(shù)時,怎么調(diào)用都不成功,如下圖所有的地方都沒錯:
而js里的調(diào)用方法就是寫的
大概經(jīng)過半天的測試和調(diào)試,我終于發(fā)現(xiàn)了問題所在:
這就是咱們基礎(chǔ)知識不扎實的地方了,還記得swift里的方法名是怎么定義的嗎???
js里的方法應(yīng)該寫成什么就對了呢?
經(jīng)過本大神的認真審查js里應(yīng)該這么寫navigateToCreateGroupBuyDataString才可以調(diào)到swift的方法,怎么樣是不是想起了什么
3.優(yōu)秀的第三方框架--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和發(fā)送一個call(圖解)
handler注冊
[self.bridgeregisterHandler:@"ObjC Echo"handler:^(iddata, WVJBResponseCallback responseCallback) {NSLog(@"ObjC Echo called with:%@", data);responseCallback(data);}];
發(fā)送call
[self.bridgecallHandler:@"JS Echo"data:nilresponseCallback:^(idresponseData) {NSLog(@"ObjC received response:%@", responseData);}];
4) 把下述代碼復(fù)制到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的開發(fā)人員外,也要讓后臺的人了解這個框架,并在合適的位置注入上述JS代碼,雖然是比較麻煩,但是這個框架確實挺好用,推薦指數(shù)5顆星!!!