本來打算第二天就寫呢辜纲,結(jié)果一直忙到了周五笨觅,想想怕忘了寫拦耐,于是擠了一點(diǎn)時(shí)間來把剩下的給大家補(bǔ)上,上篇文章介紹的swift調(diào)用js(文章地址?)见剩,這篇文章介紹js調(diào)用swift
1.JS調(diào)用OC:webView攔截鏈接的方法
此方法本人并沒有測(cè)試杀糯,是直接copy過來的,因?yàn)楦杏X此方法不是很好
-(BOOL)webView:(UIWebView *)webView
shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType;
實(shí)現(xiàn)以上webView的代理方法苍苞,當(dāng)webView每次開始加載URL時(shí)會(huì)進(jìn)入這個(gè)方法固翰,我們便可以在這個(gè)方法實(shí)現(xiàn)JS調(diào)用OC。
JS代碼如下:
OC代碼如下:
如上圖當(dāng)JS中window.location.href = "iOS:shareToTest"的代碼被觸發(fā)柒啤,會(huì)進(jìn)入OC中的這個(gè)代理方法倦挂,并且獲得"iOS:shareToTest"這個(gè)字符串,接下進(jìn)行一系列的字符串解釋担巩,得到需要被實(shí)現(xiàn)的方法名且調(diào)用方援。如果需要傳值可把需要傳的值拼接在字符串上,字符串解釋后獲取響應(yīng)的值后調(diào)用一下方法:
這種JS調(diào)用OC的方法的缺點(diǎn)十分明顯涛癌,需要繁瑣地解釋字符串得到相應(yīng)的方法名和傳值犯戏,且最多只能有兩個(gè)值,調(diào)用的方法也不能傳遞返回值拳话;但是也有一個(gè)優(yōu)點(diǎn):不需要等待頁面加載完才觸發(fā)先匪,當(dāng)相應(yīng)的代碼被運(yùn)行就能調(diào)用OC的方法,這也是下面要講到的JavaScriptCore的一個(gè)小坑弃衍。
2.蘋果推薦的框架--JavaScriptCore
這種方法是利用iOS7后新出的框架實(shí)現(xiàn)的呀非,跟我上文swift調(diào)用js 第二個(gè)方法是配套使用的,下面上代碼:
首先創(chuàng)建一個(gè)類JSObjCModel和JavaScriptSwiftDelegate镜盯,代理里面寫的是js可以調(diào)用的方法岸裙,JSObjCModel這個(gè)名字需要跟前端的小伙伴一起約定好的,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)建的那個(gè)類注入到j(luò)s里面速缆,那么js就可以通過這個(gè)類去調(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這個(gè)模型注入到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ù)制了剧董,直接截圖了),這個(gè)OCModel必須跟你的小伙伴商量好才可以破停,注意你在webViewDidFinishLoad里注入模型的時(shí)候?qū)懙谋仨氁恢虏判?/p>
但是注意這個(gè)框架最需要強(qiáng)調(diào)的一點(diǎn)是:JS調(diào)用OC時(shí)翅楼,是需要等瀏覽器加載完頁面后才能進(jìn)行交互(相當(dāng)坑、很坑U媛R汶!)晤碘,這個(gè)是需要看需求的褂微,如果你需要在網(wǎng)頁加載的時(shí)候就調(diào)用功蜓,就放棄這個(gè)方吧,繼續(xù)看下面的第三種辦法
這個(gè)方法邊寫邊發(fā)現(xiàn)了問題宠蚂,問題如下調(diào)用2個(gè)參數(shù)時(shí)式撼,怎么調(diào)用都不成功,如下圖所有的地方都沒錯(cuò):
而js里的調(diào)用方法就是寫的
大概經(jīng)過半天的測(cè)試和調(diào)試求厕,我終于發(fā)現(xiàn)了問題所在:
這就是咱們基礎(chǔ)知識(shí)不扎實(shí)的地方了著隆,還記得swift里的方法名是怎么定義的嗎?呀癣?美浦?
js里的方法應(yīng)該寫成什么就對(duì)了呢?
經(jīng)過本大神的認(rèn)真審查js里應(yīng)該這么寫navigateToCreateGroupBuyDataString才可以調(diào)到swift的方法项栏,怎么樣是不是想起了什么
3.優(yōu)秀的第三方框架--WebViewJavascriptBridge
由于我利用第二個(gè)就解決了需求浦辨,但是我還是感覺第三種方法最好,目前這個(gè)庫還在更新中沼沈,我也沒有用我的swift項(xiàng)目中流酬,但是目測(cè)這是最好的解決方法,寫到這里我還是忍不住列另,相對(duì)它嘗試一下
先奉上這個(gè)框架的GitHub地址WebViewJavascriptBridge
具體用法在GitHub上說的挺詳細(xì)的芽腾,下面大概說一下吧:
1) 首先把第三方加入你的項(xiàng)目并引用文件
#import"WebViewJavascriptBridge.h"
...
@property WebViewJavascriptBridge* bridge;
2) 注冊(cè)一個(gè)WebViewJavascriptBridge的對(duì)象 可以用 WKWebView, UIWebView (iOS) or WebView (OSX):
self.bridge = [WebViewJavascriptBridgebridgeForWebView:webView];
3) oc里面注冊(cè)一個(gè)Handler和發(fā)送一個(gè)call(圖解)
handler注冊(cè)
[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)? ? })})
如果真的要用到這個(gè)框架,除了iOS的開發(fā)人員外页衙,也要讓后臺(tái)的人了解這個(gè)框架摊滔,并在合適的位置注入上述JS代碼,雖然是比較麻煩店乐,但是這個(gè)框架確實(shí)挺好用艰躺,推薦指數(shù)5顆星!O斐病描滔!