iOS下Hybird的實(shí)現(xiàn)(一)---UIWebView與WKWebView

最近公司要使用Hybird混合開(kāi)發(fā),所以就要學(xué)習(xí)一下JS與Swift的交互,以便之后的工作;據(jù)我所知,iOS下JS與原生的交互有很多種具體有:

  • 使用UIWebView與WKWebView的代理方法,在JS 中做一次URL跳轉(zhuǎn),然后在Swift中攔截跳轉(zhuǎn)
  • 使用WKWebView 的MessageHandler
  • 使用系統(tǒng)庫(kù)JavaScriptCore,來(lái)做相互調(diào)用(iOS 7推出的)
  • 使用第三方庫(kù)WebViewJavascriptBridge
  • 使用第三方cordova庫(kù),以前叫PhoneGap(這是一個(gè)庫(kù)平臺(tái)的庫(kù))
  • 使用React Native

本文主要是 寫使用UIWebView與WKWebView的代理方法,在JS 中做一次URL跳轉(zhuǎn),然后在Swift中攔截跳轉(zhuǎn) 的情況

1. 使用的情景

當(dāng)我們?cè)谂cJS交互的接口比較少時(shí),就適用這種情況

2. UIWebView的情景

picture.gif

首先,創(chuàng)建UIWebView,并加載本地HTML

    lazy var webView: UIWebView = {[unowned self] in
        let view     = UIWebView(frame: self.view.bounds)
        let htmlURL  = Bundle.main.url(forResource: "anran.html", withExtension: nil)
        let request  = URLRequest(url: htmlURL!)
        let request1 =  URLRequest(url: URL(string: "https://www.baidu.com")!)
        view.scrollView.decelerationRate = UIScrollViewDecelerationRateNormal
        view.loadRequest(request)
        view.scrollView.bounces = false
        view.delegate = self
        return view
    }()

然后,在HTML里,定義按鈕,來(lái)觸發(fā)調(diào)用原生的方法,然后再將執(zhí)行結(jié)果回調(diào)到j(luò)s 里。

        <input type="button" value="點(diǎn)我點(diǎn)我" onclick="scanClick()" />
        <input type="button" value="獲取定位" onclick="locationClick()" />
        <input type="button" value="修改導(dǎo)航欄顏色" onclick="colorClick()" />
        <input type="button" value="分享" onclick="shareClick()" />
        <input type="button" value="支付" onclick="payClick()" />
        <input type="button" value="動(dòng)次打次" onclick="shake()" />
        <input type="button" value="返回" onclick="goBack()" />
     function loadURL(url) {
         var iFrame;
         iFrame = document.createElement("iframe");
         iFrame.setAttribute("src", url);
         iFrame.setAttribute("style", "display:none;");
         iFrame.setAttribute("height", "0px");
         iFrame.setAttribute("width", "0px");
         iFrame.setAttribute("frameborder", "0");
         document.body.appendChild(iFrame);
          // 發(fā)起請(qǐng)求后這個(gè)iFrame就沒(méi)用了,所以把它從dom上移除掉
         iFrame.parentNode.removeChild(iFrame);
         iFrame = null;
     }

1.為什么自定義一個(gè)loadURL 方法,不直接使用window.location.href?
答:因?yàn)槿绻?dāng)前網(wǎng)頁(yè)正使用window.location.href加載網(wǎng)頁(yè)的同時(shí),調(diào)用window.location.href去調(diào)用OC原生方法,會(huì)導(dǎo)致加載網(wǎng)頁(yè)的操作被取消掉。
同樣的,如果連續(xù)使用window.location.href執(zhí)行兩次OC原生調(diào)用,也有可能導(dǎo)致第一次的操作被取消掉。所以我們使用自定義的loadURL,來(lái)避免這個(gè)問(wèn)題。
loadURL的實(shí)現(xiàn)來(lái)自關(guān)于UIWebView和PhoneGap的總結(jié)一文。
2.為什么loadURL 中的鏈接,使用統(tǒng)一的scheme?
答:便于在OC 中做攔截處理,減少在JS中調(diào)用一些OC 沒(méi)有實(shí)現(xiàn)的方法時(shí),webView 做跳轉(zhuǎn)。因?yàn)槲以贠C 中攔截URL 時(shí),根據(jù)scheme (即haleyAction)來(lái)區(qū)分是調(diào)用原生的方法還是正常的網(wǎng)頁(yè)跳轉(zhuǎn)。然后根據(jù)host(即//后的部分getLocation)來(lái)區(qū)分執(zhí)行什么操作。
3.為什么自定義一個(gè)asyncAlert方法?
答:因?yàn)橛械腏S調(diào)用是需要OC 返回結(jié)果到JS的。stringByEvaluatingJavaScriptFromString是一個(gè)同步方法,會(huì)等待js 方法執(zhí)行完成,而彈出的alert 也會(huì)阻塞界面等待用戶響應(yīng),所以他們可能會(huì)造成死鎖。導(dǎo)致alert 卡死界面。如果回調(diào)的JS 是一個(gè)耗時(shí)的操作,那么建議將耗時(shí)的操作也放入setTimeout的function 中。

最后,攔截URL也就是自定義的協(xié)議,UIWebView 有一個(gè)代理方法,可以攔截到每一個(gè)鏈接的Request。return true,webView 就會(huì)加載這個(gè)鏈接;return false,webView 就不會(huì)加載這個(gè)連接,我們就在這個(gè)攔截的代理方法中處理自己的URL

    func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
        
        let url = request.url
        let scheme = url?.scheme
        
        if let URL = url, scheme == "anranaction" {
            self.handleCustomAction(url: URL)
            return false
        }
        
        return true
    }

這里通過(guò)scheme,來(lái)攔截掉自定義的URL 就非常容易了,如果不同的方法使用不同的scheme,那么判斷起來(lái)就非常的麻煩,看看我們是怎么處理的

    // MARK: - 處理URL然后調(diào)用方法
    func handleCustomAction(url: URL) {
        
        let host = url.host
        
        if host == "scanClick" {
        
        } else if host == "shareClick" {
            share(url: url)
        } else if host == "getLocation" {
            getLocation()
        } else if host == "setColor" {
            ChangeColor(url: url)
        } else if host == "payAction" {
            payAction(url: url)
        } else if host == "shake" {
            sharedAction()
        } else if host == "back" {
            goBack()
        }
        
    }

我們?cè)贘S中調(diào)用Swift 方法的時(shí)候,也需要傳參數(shù)到Swift 中,怎么傳呢?
就像一個(gè)get 請(qǐng)求一樣,把參數(shù)放在后面:

    function shareClick() {
        loadURL("haleyAction://shareClick?title=標(biāo)題&content=內(nèi)容        &url=http://www.baidu.com");
    }

我們?nèi)绾潍@取參數(shù),并且將參數(shù)傳回JS中,所有的參數(shù)都在URL的query中,先通過(guò)&將字符串拆分,在通過(guò)=把參數(shù)拆分成key 和實(shí)際的值

    func share(url: URL) {
       
        guard let params = url.query?.components(separatedBy: "&") else { return }
       
        var tempDic = [String:Any]()
        for paramStr in params {
            let dicArray = paramStr.components(separatedBy: "=")
            if dicArray.count > 1 {
                guard let str = dicArray[1].removingPercentEncoding else { return }
                tempDic[dicArray[0]] = str
            }
        }
    
        let title = tempDic["title"]
        let content = tempDic["content"]
        let url = tempDic["url"]

        let jsStr = "shareResult('\(title ?? "")','\(content ?? "")','\(url ?? "")')"
        
        webView.stringByEvaluatingJavaScript(from: jsStr)
    }

Swift調(diào)用JS

    let jsStr = "setLocation('\("杭州市拱墅區(qū)下沙中國(guó)計(jì)量學(xué)院")')"
    webView.stringByEvaluatingJavaScript(from: jsStr)

Swift中可以往HMTL的JS環(huán)境中插入全局變量、JS方法

    func webViewDidFinishLoad(_ webView: UIWebView) {
        print("webView加載完成然后調(diào)用")
        webView.stringByEvaluatingJavaScript(from: "var arr = [3, 4, 'abc']")
    }

3. WKWebView的情景

picture1.gif

由于UIWebView比較耗內(nèi)存,性能上不太好,而蘋果在iOS 8中推出了WKWebView。
同樣的用WKWebView也可以攔截URL,做JS 與Native交互

安然 打開(kāi)百度網(wǎng)頁(yè)前 打開(kāi)百度網(wǎng)頁(yè)后
UIWebView 內(nèi)存47M 內(nèi)存75.6M,最高峰83M
WKWebView 內(nèi)存47M 內(nèi)存51M

盡管WKWebView有很多的優(yōu)點(diǎn)但是也有很多的缺點(diǎn),比如他的儲(chǔ)存模式,是封閉的,我們要訪問(wèn)也是不容易的,這個(gè)問(wèn)題在以后我專門的學(xué)習(xí)一下,這篇就不在解釋了

WKWebView 與 UIWebView 攔截URL 的處理方式基本一樣。除了代理方法和WKWebView的使用不太一樣,關(guān)于WKWebView更詳盡的講解和用法,還是自行搜索學(xué)習(xí),本文重點(diǎn)還是講解如何實(shí)現(xiàn)JS 與Native互相調(diào)用

首先, 創(chuàng)建WKWebView,WKWebView的創(chuàng)建有幾點(diǎn)不同:

  • 初始化configuration參數(shù),當(dāng)然這個(gè)參數(shù)我們也可以不傳,直接使用默認(rèn)的設(shè)置就好
  • WKWebView的代理有兩個(gè)navigationDelegate和UIDelegate。我們要攔截URL,就要通過(guò)navigationDelegate的一個(gè)代理方法來(lái)實(shí)現(xiàn)。如果在HTML中要使用alert等彈窗,就必須得實(shí)現(xiàn)UIDelegate的相應(yīng)代理方法
  • 在iOS 9之前,WKWebView加載本地HTML會(huì)有一些問(wèn)題(不能加載本地HTML,或者部分CSS/本地圖片加載不了等)
    lazy var webView: WKWebView = {[unowned self] in
        
        let configuration = WKWebViewConfiguration()
        configuration.userContentController = WKUserContentController()
        let preferences = WKPreferences()
        preferences.javaScriptCanOpenWindowsAutomatically = true
        preferences.minimumFontSize = 30.0
        configuration.preferences = preferences
        let view = WKWebView(frame: self.view.frame, configuration: configuration)
        let urlStr = Bundle.main.path(forResource: "anran.html", ofType: nil)
        let fileURL = URL(fileURLWithPath: urlStr!)
        view.loadFileURL(fileURL, allowingReadAccessTo: fileURL)
        view.navigationDelegate = self
        view.uiDelegate = self
        return view
    }()

然后,使用WKNavigationDelegate中的代理方法,攔截自定義的URL來(lái)實(shí)現(xiàn)JS調(diào)用OC方法

  • 如果實(shí)現(xiàn)了這個(gè)代理方法,就必須得調(diào)用decisionHandler這個(gè)block,否則會(huì)導(dǎo)致app 崩潰。block參數(shù)是個(gè)枚舉類型,WKNavigationActionPolicyCancel代表取消加載,相當(dāng)于UIWebView的代理方法return false的情況;WKNavigationActionPolicyAllow代表允許加載,相當(dāng)于UIWebView的代理方法中 return true的情況
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        let url = navigationAction.request.url
        let scheme = url?.scheme
        
        if let URL = url, scheme == "anranaction" {
            self.handleCustomAction(url: URL)
            // 一定要實(shí)現(xiàn)否則會(huì)崩潰
            decisionHandler(.cancel)
            return
        }
        decisionHandler(.allow)
    }

最后,JS 調(diào)用Native方法后,有的操作可能需要將結(jié)果返回給JS。這時(shí)候就是Native 調(diào)用JS 方法的場(chǎng)景,WKWebView 提供了一個(gè)新的方法evaluateJavaScript:completionHandler:實(shí)現(xiàn)Native調(diào)用JS 等場(chǎng)景

    func getLocation() {
        let jsStr = "setLocation('\("杭州市拱墅區(qū)下沙中國(guó)計(jì)量學(xué)院")')"
        webView.evaluateJavaScript(jsStr) { (result, error) in
            print("\(result)")
        }
    }

這個(gè)方法evaluateJavaScript(<#T##javaScriptString: String##String#>, completionHandler: <#T##((Any?, Error?) -> Void)?##((Any?, Error?) -> Void)?##(Any?, Error?) -> Void#>)沒(méi)有返回值,JS 執(zhí)行成功還是失敗會(huì)在completionHandler 中返回。所以使用這個(gè)API 就可以避免執(zhí)行耗時(shí)的JS,或者alert 導(dǎo)致界面卡住的問(wèn)題

WKWebView中使用彈窗
在上面提到,如果在WKWebView中使用alert、confirm 等彈窗,就得實(shí)現(xiàn)WKWebView的WKUIDelegate中相應(yīng)的代理方法。
如果,我在JS中要顯示alert 彈窗,就必須實(shí)現(xiàn)如下代理方法,否則alert 并不會(huì)彈出

    func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
        
        let alert = UIAlertController(title: "提醒", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "知道", style: .cancel, handler: { (action) in
            completionHandler()
        }))
        
        self.present(alert, animated: true, completion: nil)
    }

其中completionHandler這個(gè)block 必須調(diào)用

4. 總結(jié)這只是簡(jiǎn)單的實(shí)用,本文已用了大神的HTML與方法,在Swift中實(shí)現(xiàn)請(qǐng)看源碼,要是覺(jué)得不錯(cuò)請(qǐng)給個(gè)星星

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,179評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 175,628評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,642評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,444評(píng)論 6 405
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 54,948評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,185評(píng)論 0 287
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,717評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,602評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,794評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,045評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,418評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,671評(píng)論 1 281
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,414評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,750評(píng)論 2 370

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

  • 前言 關(guān)于UIWebView的介紹,相信看過(guò)上文的小伙伴們,已經(jīng)大概清楚了吧,如果有問(wèn)題,歡迎提問(wèn)。 本文是本系列...
    Dark_Angel閱讀 28,970評(píng)論 67 291
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,149評(píng)論 4 61
  • 語(yǔ)法 1. 標(biāo)題 格式: 顯示效果: 一級(jí)標(biāo)題 二級(jí)標(biāo)題 三級(jí)標(biāo)題 四級(jí)標(biāo)題 五級(jí)標(biāo)題 六級(jí)標(biāo)題 2 .列表 2....
    AlerStar閱讀 254評(píng)論 0 0
  • 前幾天看了一部朋友推薦了很久的片子《肖申克的救贖》。看完真的特別震驚但更多的應(yīng)該是感動(dòng)。當(dāng)我看著安迪在磅礴大...
    念頭閱讀 546評(píng)論 0 0