iOS下webView和JS交互

背景

  • h5和native 交互代碼冗余 不清晰 多人開(kāi)發(fā)時(shí)效率地下

目的

  • 為了尋找更搞笑的編程開(kāi)發(fā)方式蹬跃,節(jié)省代碼量木柬,以及多人開(kāi)發(fā)的效率

過(guò)程探索

JavaScriptCore

  • 官方解釋
    The JavaScriptCore Framework provides the ability to evaluate JavaScript programs from within Swift, Objective-C, and C-based apps. You can use also use JavaScriptCore to insert custom objects to the JavaScript environment.
    JavaScriptCore框架提供了從Swift、Objective-C和基于c的應(yīng)用程序中評(píng)估JavaScript程序的能力府喳。您還可以使用JavaScriptCore將定制對(duì)象插入到JavaScript環(huán)境中落塑。
    蘋(píng)果爸爸在iOS7.0以后推出的官方庫(kù),目前看起來(lái)是適用于UIWebView(官方以不推薦使用)

下面說(shuō)一下使用方法

關(guān)鍵詞: JSContext JSValue JSExport

  • JSExport
    繼承并申明你需要到protocol - 注入到JS里到function
@objc protocol SwiftJavaScriptProtocol: JSExport {
    func test(_ value: String?)
    // js調(diào)用App的功能后 App再調(diào)用js函數(shù)執(zhí)行回調(diào)
    func callHandler(handleFuncName: String)

    var stringCallback: (@convention(block) (String) -> String)? { get set }
}

申明一個(gè)繼承于NSObjectt的class, 并實(shí)現(xiàn)你的protocol

class SwiftJavaScriptModel: NSObject, SwiftJavaScriptProtocol  {
    weak var jsContext: JSContext? //js 的執(zhí)行環(huán)境盛泡,調(diào)用js 或者注入js
    var stringCallback: (@convention(block) (String) -> String)? // swift里想要讓JS能夠調(diào)用我們的clourse,一定要用這個(gè)關(guān)鍵字

    func test(_ value: String?) {
        print("js call test(_ value: \(value ?? ""))")
    }

    func callHandler(handleFuncName: String) {
        let jsHandlerFunc = self.jsContext?.objectForKeyedSubscript("\(handleFuncName)")
        let dict = ["name": "sean", "age": 18] as [String : Any]
        jsHandlerFunc?.call(withArguments: [dict])
    }

    override init() {
        super.init()
        self.stringCallback = {
            [weak self] value in
            print("SwiftJavaScriptModel stringCallback \(value)")
            return value.appending("this is append string")

        }
    }
}


最后椎工,通過(guò)UIWebView的特性饭于,獲取JSContext,并將我們的function注入到上下文

extension JSCoreVC: UIWebViewDelegate {
    func webViewDidFinishLoad(_ webView: UIWebView) {
        if let jsContext = webView.value(forKeyPath:"documentView.webView.mainFrame.javaScriptContext") as? JSContext {
            let model = SwiftJavaScriptModel()
            model.jsContext = jsContext
            jsContext.setObject(model, forKeyedSubscript: ("WebViewJavascriptBridge" as NSString)) //注入
            jsContext.evaluateScript(self.htmlContent)
            jsContext.exceptionHandler = {
                [weak self] (context, exception) in
                guard let self = self else { return }
                print("exception:", exception)

            }
        }
    }
}

  • WebViewJavascriptBridge 注意蜀踏,這里注入的名字即是JS調(diào)用的名字维蒙,JS調(diào)用我們的函數(shù)和自己的使用方式一樣,舉例如下
<div class="btn-block" onclick="WebViewJavascriptBridge.test('this is toast')">
    test
</div>
<div class="btn-block" onclick="appStringCallbackFunc('this is toast')">
    stringCallback
</div>
<script type="text/javascript">
    function appStringCallbackFunc(arg) {
        let value = WebViewJavascriptBridge.stringCallback('this is toast')
        document.getElementById('js-content').innerHTML = "App調(diào)用js回調(diào)函數(shù)啦, 返回 ->" + value;
    }
</script>

看起來(lái)很方便吧果覆,app -> JS 也很方便

let context = JSContext()
let _ = context?.evaluateScript("var triple = (value) => value + 3") //注入
let returnV = context?.evaluateScript("triple(3)") // 調(diào)用
print("__testValueInContext --- returnValue = \(returnV?.toNumber())")
        

WKWebView

  • 官方解釋
    You can make POST requests with httpBody content in a WKWebView.
    After creating a new WKWebView object using the init(frame:configuration:) method, you need to load the web content. Use the loadHTMLString(:baseURL:) method to begin loading local HTML files or the load(:) method to begin loading web content. Use the stopLoading() method to stop loading, and the isLoading property to find out if a web view is in the process of loading. Set the delegate property to an object conforming to the WKUIDelegate protocol to track the loading of web content. See Listing 1 for an example of creating a WKWebView programmatically.
    你可以在WKWebView中用httpBody內(nèi)容發(fā)出POST請(qǐng)求颅痊。
    在使用init(frame:configuration:)方法創(chuàng)建一個(gè)新的WKWebView對(duì)象之后,您需要加載web內(nèi)容局待。使用loadHTMLString(:baseURL:)方法開(kāi)始加載本地HTML文件斑响,或使用load(:)方法開(kāi)始加載web內(nèi)容。使用stopLoading()方法停止加載钳榨,使用isLoading屬性查明web view是否在加載過(guò)程中舰罚。將委托屬性設(shè)置為符合WKUIDelegate協(xié)議的對(duì)象,以跟蹤web內(nèi)容的加載薛耻。清單1給出了以編程方式創(chuàng)建WKWebView的示例营罢。

WKWebView關(guān)鍵詞

WKUIDelegate WKNavigationDelegate WKScriptMessageHandler

/*
     實(shí)現(xiàn)原理:
     1、JS與iOS約定好xdart協(xié)議饼齿,用作JS在調(diào)用iOS時(shí)url的scheme饲漾;
     2、JS拿到的url:(xdart://lot_detail?id=123)缕溉;
     3考传、iOS的WKWebView在請(qǐng)求跳轉(zhuǎn)前會(huì)調(diào)用-webView:decidePolicyForNavigationAction:decisionHandler:方法來(lái)確認(rèn)是否允許跳轉(zhuǎn);
     4证鸥、iOS在此方法內(nèi)截取xdart協(xié)議獲取JS傳過(guò)來(lái)的數(shù)據(jù)僚楞,執(zhí)行內(nèi)部schema跳轉(zhuǎn)邏輯
     5勤晚、通過(guò)decisionHandler(.cancel)可以設(shè)置不允許此請(qǐng)求跳轉(zhuǎn) decisionHandler一定要調(diào)用
     */
    //! WKWeView在每次加載請(qǐng)求前會(huì)調(diào)用此方法來(lái)確認(rèn)是否進(jìn)行請(qǐng)求跳轉(zhuǎn)
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        guard let url = navigationAction.request.url else {return}
        guard let scheme = url.scheme else {return}

        if scheme == "xdart" {
            // THSchemeManager.handleScheme(url.absoluteString)
        }
        decisionHandler(.allow)

    }

WKUIDelegate 基于JS系統(tǒng)的幾個(gè)內(nèi)部方法 實(shí)現(xiàn)一下方法要調(diào)用對(duì)應(yīng)的completionHandler,否則崩潰

func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
        // call toast
        completionHandler()
}

func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
        // call alert
        completionHandler(true)
}
func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
        // call input
        completionHandler("this is a message")
}

WKScriptMessageHandler 重點(diǎn)來(lái)了泉褐,這是蘋(píng)果爸爸推薦使用的JS交互

let content = WKUserContentController()
content.add(self, name: "artproFunc") //此方法會(huì)造成循環(huán)引用运翼,注意時(shí)機(jī)釋放
let config = WKWebViewConfiguration()
config.userContentController = content
wkwebV = WKWebView.init(frame: self.view.bounds, configuration: config)
  • 這里往content里注入的是JS調(diào)用APP的函數(shù)
    JS通過(guò)window.webkit.messageHandlers.artproFunc.postMessage()給artproFunc發(fā)送消息
    我們的解析在此方法里
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name != self.applicationName { return }
        if let body = message.body as? [String: Any],
           let method = body["method"] as? String {
            let aSel = NSSelectorFromString(method)
            if !self.canPerformAction(aSel, withSender: nil) {
                return
            }
            let para = body["parameter"]
            let callback = body["callback"]
        }
}

以我目前的項(xiàng)目為例,我們是將 artproFunc當(dāng)作了一個(gè)通道兴枯,所有的function都走message.body分發(fā)出來(lái)血淌,所以會(huì)有switch case 解析 body中的method,然后再進(jìn)行不同的方法分發(fā)财剖。

缺點(diǎn)

  • 代碼冗余
  • 字符串分發(fā)悠夯,容易出錯(cuò)
  • 函數(shù)格式各式各樣,多人開(kāi)發(fā)躺坟,他人不好接手


    artpro_userContent.png

WebViewJavascriptBridge

最近研究JS和iOS native交互沦补,偶然發(fā)現(xiàn)的庫(kù)發(fā)現(xiàn)github上用的人也不少,感覺(jué)還不錯(cuò)的樣子,就研究了下使用方法

WebViewJavascriptBridge_star.png

看起來(lái)很簡(jiǎn)單的樣子

bridge = WebViewJavascriptBridge.init(webV)
// js call app
bridge.registerHandler("testCallHandler") { [weak self](data, callback) in
    guard let self = self else { return }
    callback?("Response from testCallHandler")
}
// app call js
self.bridge.callHandler("testJavascriptHandler", data: ["foo": "before ready"])

WebViewJavascriptBridge 原理將在下篇文章中剖析
參考文檔1

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末咪橙,一起剝皮案震驚了整個(gè)濱河市夕膀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌美侦,老刑警劉巖产舞,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異菠剩,居然都是意外死亡易猫,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)具壮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)准颓,“玉大人,你說(shuō)我怎么就攤上這事棺妓∪烈眩” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵怜跑,是天一觀的道長(zhǎng)样勃。 經(jīng)常有香客問(wèn)我,道長(zhǎng)妆艘,這世上最難降的妖魔是什么彤灶? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮批旺,結(jié)果婚禮上幌陕,老公的妹妹穿的比我還像新娘。我一直安慰自己汽煮,他們只是感情好搏熄,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布棚唆。 她就那樣靜靜地躺著,像睡著了一般心例。 火紅的嫁衣襯著肌膚如雪宵凌。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,328評(píng)論 1 310
  • 那天止后,我揣著相機(jī)與錄音瞎惫,去河邊找鬼。 笑死译株,一個(gè)胖子當(dāng)著我的面吹牛瓜喇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播歉糜,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼乘寒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了匪补?” 一聲冷哼從身側(cè)響起伞辛,我...
    開(kāi)封第一講書(shū)人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎夯缺,沒(méi)想到半個(gè)月后蚤氏,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡喳逛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年瞧捌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片润文。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖殿怜,靈堂內(nèi)的尸體忽然破棺而出典蝌,到底是詐尸還是另有隱情,我是刑警寧澤头谜,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布骏掀,位于F島的核電站,受9級(jí)特大地震影響柱告,放射性物質(zhì)發(fā)生泄漏截驮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一际度、第九天 我趴在偏房一處隱蔽的房頂上張望葵袭。 院中可真熱鬧,春花似錦乖菱、人聲如沸坡锡。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鹉勒。三九已至帆锋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間禽额,已是汗流浹背锯厢。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留脯倒,地道東北人哲鸳。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像盔憨,于是被迫代替她去往敵國(guó)和親徙菠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容