【Swift】WKWebView與JS的交互使用


一、前言  

  近日,有朋友問我關於WKWebView與JS的交互問題,可我之前一直使用的是UIWebView,也不曾做過WKWebView的交互啊!接下來大家一塊學習下WKWebView是怎么實現原生代碼和JS交互的。2016年時候曾寫過一篇關於UIWebView與JS的交互。傳送門>>>

二、WKWebView

  • 支持更多的HTML5的特性

  • 高達60fps滾動刷新頻率與內置手勢

  • 與Safari相容的JavaScript引擎

  • 在性能、穩定性方面有很大提升占用內存更少 協議方法及功能都更細致

  • 可獲取加載進度等。

三、WKWebView的代理方法

    /*! @abstract The web view's navigation delegate. */
    weak open var navigationDelegate: WKNavigationDelegate?
    
    /*! @abstract The web view's user interface delegate. */
    weak open var uiDelegate: WKUIDelegate?

三、WKNavigationDelegate的代理方法

 //判斷鏈接是否允許跳轉
   optional func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void)
     
    //拿到響應后決定是否允許跳轉
    optional func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void)
     
    //鏈接開始加載時調用
   optional func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!)
     
    //收到服務器重定向時調用
   optional func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!)

     
    //加載錯誤時調用
   optional func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error)
     
    //當內容開始到達主幀時被調用(即將完成)
    optional func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!)
     
    //加載完成
    optional func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)
     
    //在提交的主幀中發生錯誤時調用
    optional func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error)
     
    //當webView需要響應身份驗證時調用(如需驗證服務器證書)
    optional func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
     
    //當webView的web內容進程被終止時調用。(iOS 9.0之后)
   optional func webViewWebContentProcessDidTerminate(_ webView: WKWebView)

四、WKUIDelegate的代理方法

  用來做一些頁面上的事件,彈窗警告,提醒等。

    //接收到警告面板
    optional func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void)
     
    //接收到確認面板
   optional func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void)
     
    //接收到輸入框
   optional func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void)

五、WKWebView與JS的交互使用

  首頁創建html文件,代碼如下:

<html lang="en">
    
    <head>
        <meta charset="utf-8" />
        <title>JS交互</title>
        
        <style>
            
            body {
                font-size:30px;
                text-align:center;
            }
        
        * {
            margin: 30px;
            padding: 0;
        }
        
        h1{
            color: red;
        }
        
        button{
            width: 300px;
            height: 50px;
            font-size: 30px;
        }
        
            </style>
        
    </head>
    <body>
        <h1>WKWebview與iOS交互</h1>
        <h2></h2>
        <button onclick="testA()">點擊alert彈框</button>
        <button onclick="testB('我是彈窗內容')">點擊alert有參彈窗</button>
        <button onclick="testConfrim()">點擊confrim彈窗</button>
        <button onclick="buttonAction()">向iOS端傳遞數據</button>
        <script type="text/javascript">
            
            //無參數函數
            function testA() {
                alert("我是JS中的彈窗消息");
            }
        
            //有參數函數
            function testB(value) {
                alert(value);
            }
        
            function testC(value) {
                return value + "value";
            }
        
            //接受iOS端傳過來的參數,
            function testObject(name,age) {
                var object = {name:name,age:age};
                return object;
            }
        
            function testConfrim() {
                comfirm("確定修改數據嗎?")
            }
        
            function buttonAction(){
                try {
                    <!-- js 向iOS 傳遞數據-->
                    window.webkit.messageHandlers.getMessage.postMessage("我是js傳遞過來的數據")
                }catch (e) {
                    console.log(e)
                }
            }
        </script>
        
   
    </body>
</html>

   1、iOS調用js中的方法進行並傳參

//案例1
self.webView?.evaluateJavaScript("testInput('123')", completionHandler: { (data
            , error) in
            print(data as Any)
        })
//案例2
self.webView?.evaluateJavaScript("testObject('xjf',26)", completionHandler: { (data, err) in
            print("\(String(describing: data)),\(String(describing: err))")
        })

  2、js向iOS傳遞數據

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        print("\(message.name)" + "\(message.body)")
//        message.name 方法名
//        message.body 傳遞的數據
    }

  3、在js中點擊按鈕,進行彈窗實現

    //MARK:WKUIDelegate
    //此方法作為js的alert方法接口的實現,默認彈出窗口應該只有提示消息,及一個確認按鈕,當然可以添加更多按鈕以及其他內容,但是並不會起到什么作用
    //點擊確認按鈕的相應事件,需要執行completionHandler,這樣js才能繼續執行
    ////參數 message為  js 方法 alert(<message>) 中的<message>
    func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
        let alertViewController = UIAlertController(title: "提示", message:message, preferredStyle: UIAlertController.Style.alert)
        alertViewController.addAction(UIAlertAction(title: "確認", style: UIAlertAction.Style.default, handler: { (action) in
            completionHandler()
        }))
        self.present(alertViewController, animated: true, completion: nil)
    }
    
    // confirm
    //作為js中confirm接口的實現,需要有提示信息以及兩個相應事件, 確認及取消,並且在completionHandler中回傳相應結果,確認返回YES, 取消返回NO
    //參數 message為  js 方法 confirm(<message>) 中的<message>
    func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
        let alertVicwController = UIAlertController(title: "提示", message: message, preferredStyle: UIAlertController.Style.alert)
        alertVicwController.addAction(UIAlertAction(title: "取消", style: UIAlertAction.Style.cancel, handler: { (alertAction) in
            completionHandler(false)
        }))
        alertVicwController.addAction(UIAlertAction(title: "確定", style: UIAlertAction.Style.default, handler: { (alertAction) in
            completionHandler(true)
        }))
        self.present(alertVicwController, animated: true, completion: nil)
    }
    
    // prompt
    //作為js中prompt接口的實現,默認需要有一個輸入框一個按鈕,點擊確認按鈕回傳輸入值
    //當然可以添加多個按鈕以及多個輸入框,不過completionHandler只有一個參數,如果有多個輸入框,需要將多個輸入框中的值通過某種方式拼接成一個字符串回傳,js接收到之后再做處理
    //參數 prompt 為 prompt(<message>, <defaultValue>);中的<message>
    //參數defaultText 為 prompt(<message>, <defaultValue>);中的 <defaultValue>
    func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
        let alertViewController = UIAlertController(title: prompt, message: "", preferredStyle: UIAlertController.Style.alert)
        alertViewController.addTextField { (textField) in
            textField.text = defaultText
        }
        alertViewController.addAction(UIAlertAction(title: "完成", style: UIAlertAction.Style.default, handler: { (alertAction) in
            completionHandler(alertViewController.textFields![0].text)
        }))
        self.present(alertViewController, animated: true, completion: nil)
    }

  4、獲取網頁中節點的數據 

//網頁加載完成
-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
    //設置JS
    NSString *js = @"document.getElementsByTagName('h1')[0].innerText";
    //執行JS
    [webView evaluateJavaScript:js completionHandler:^(id _Nullable response, NSError * _Nullable error) {
        NSLog(@"value: %@ error: %@", response, error);
        
    }];
}

  5、通過注入JS修改節點的內容

let js = "document.getElementsByTagName('h2')[0].innerText = '這是一個iOS寫入的方法'";
//將js注入到網頁中

  6、js獲取DOM節點的幾種方式

document.getElementById();//id名,
document.getElementsByTagName();//標簽名
document.getElementsByClassName();//類名
document.getElementsByName();//name屬性值,一般不用
document.querySelector();//css選擇符模式,返回與該模式匹配的第一個元素,結果為一個元素;如果沒找到匹配的元素,則返回null
document.querySelectorAll()//css選擇符模式,返回與該模式匹配的所有元素,結果為一個類數組

 六、JavaScriptCore

  JavaScriptCore 這個庫是 Apple 在 iOS 7 之后加入到標准庫的,它對 iOS Native 與 JS 做交互調用產生了划時代的影響。

   JavaScriptCore 大體是由 4 個類以及 1 個協議組成的:

  • JSContext 是 JS 執行上下文,你可以把它理解為 JS 運行的環境。

  • JSValue 是對 JavaScript 值的引用,任何 JS 中的值都可以被包裝為一個 JSValue。

  • JSManagedValue 是對 JSValue 的包裝,加入了“conditional retain”。

  • JSVirtualMachine 表示 JavaScript 執行的獨立環境。

  還有 JSExport 協議:

實現將原生類及其實例方法,類方法和屬性導出為 JavaScript 代碼的協議。

  這里的 JSContext,JSValue,JSManagedValue 相對比較好理解,下面我們把 JSVirtualMachine 單拎出來說明一下:

  JSVirtualMachine 的用法和其與 JSContext 的關系

  JSVirtualMachine 實例表示用於 JavaScript 執行的獨立環境。 您使用此類有兩個主要目的:支持並發 JavaScript 執行,並管理 JavaScript 和 Objective-C 或 Swift 之間橋接的對象的內存。

  關於 JSVirtualMachine 的使用,一般情況下我們不用手動去創建 JSVirtualMachine。因為當我們獲取 JSContext 時,獲取到的 JSContext 從屬於一個 JSVirtualMachine。

  每個 JavaScript 上下文(JSContext 對象)都屬於一個 JSVirtualMachine。 每個 JSVirtualMachine 可以包含多個上下文,允許在上下文之間傳遞值(JSValue 對象)。 但是,每個 JSVirtualMachine 是不同的,即我們不能將一個 JSVirtualMachine 中創建的值傳遞到另一個 JSVirtualMachine 中的上下文。

  JavaScriptCore API 是線程安全的 —— 例如,我們可以從任何線程創建 JSValue 對象或運行 JS 腳本 - 但是,嘗試使用相同 JSVirtualMachine 的所有其他線程將被阻塞。 要在多個線程上同時(並發)運行 JavaScript 腳本,請為每個線程使用單獨的 JSVirtualMachine 實例。

七、案例源碼:

class NAHomeViewController : UIViewController,WKNavigationDelegate,WKScriptMessageHandler,WKUIDelegate,UINavigationControllerDelegate {
    
    var webView : WKWebView?
    var content : JSContext?
    var userContentController : WKUserContentController?
    override func viewDidLoad() {
        super.viewDidLoad()
        self.navigationItem.title = "首頁"
       
        //創建配置對象
        let configuration = WKWebViewConfiguration()
        //為WKWebViewController設置偏好設置
        let preference = WKPreferences()
        configuration.preferences = preference
        
        //允許native與js交互
        preference.javaScriptEnabled = true

        //初識化webView
        let webView = WKWebView.init(frame: CGRect(x: 0, y: 64, width: self.view.frame.size.width, height: 300))
        let path = Bundle.main.path(forResource: "wKWebView", ofType: "html")
        webView.navigationDelegate = self
        webView.uiDelegate = self
        let request = URLRequest.init(url: URL.init(fileURLWithPath: path!))
        webView.load(request)
        self.view.addSubview(webView)
        self.webView = webView
        
        let userContentController = WKUserContentController()
        configuration.userContentController = userContentController
        userContentController.add(self,name: "getMessage")
        self.userContentController = userContentController
        
        let btn = UIButton.init(frame: CGRect(x: 100, y: 390, width: 100, height: 50))
        btn.setTitleColor(.black, for: .normal)
        btn.setTitle("oc調用js", for: .normal)
        btn.addTarget(self, action: #selector(btnAction), for: .touchUpInside)
        self.view.addSubview(btn)
        
        self.view.backgroundColor = .white
    }
    
    @objc func btnAction() {
        self.webView?.evaluateJavaScript("testInput('123')", completionHandler: { (data
            , error) in
            print(data as Any)
        })
        self.webView?.evaluateJavaScript("testObject('xjf',26)", completionHandler: { (data, err) in
            print("\(String(describing: data)),\(String(describing: err))")
        })
    }
    
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        print("\(message.name)" + "\(message.body)")
//        message.name 方法名
//        message.body 傳遞的數據
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
//        webView.evaluateJavaScript("testA()") { (data, err) in
//            print("\(String(describing: data)),\(String(describing: err))")
//        }
    }
    
    func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
        print("\(error)")
    }
    
    
    //MARK:WKUIDelegate
    //此方法作為js的alert方法接口的實現,默認彈出窗口應該只有提示消息,及一個確認按鈕,當然可以添加更多按鈕以及其他內容,但是並不會起到什么作用
    //點擊確認按鈕的相應事件,需要執行completionHandler,這樣js才能繼續執行
    ////參數 message為  js 方法 alert(<message>) 中的<message>
    func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
        let alertViewController = UIAlertController(title: "提示", message:message, preferredStyle: UIAlertController.Style.alert)
        alertViewController.addAction(UIAlertAction(title: "確認", style: UIAlertAction.Style.default, handler: { (action) in
            completionHandler()
        }))
        self.present(alertViewController, animated: true, completion: nil)
    }
    
    // confirm
    //作為js中confirm接口的實現,需要有提示信息以及兩個相應事件, 確認及取消,並且在completionHandler中回傳相應結果,確認返回YES, 取消返回NO
    //參數 message為  js 方法 confirm(<message>) 中的<message>
    func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
        let alertVicwController = UIAlertController(title: "提示", message: message, preferredStyle: UIAlertController.Style.alert)
        alertVicwController.addAction(UIAlertAction(title: "取消", style: UIAlertAction.Style.cancel, handler: { (alertAction) in
            completionHandler(false)
        }))
        alertVicwController.addAction(UIAlertAction(title: "確定", style: UIAlertAction.Style.default, handler: { (alertAction) in
            completionHandler(true)
        }))
        self.present(alertVicwController, animated: true, completion: nil)
    }
    
    // prompt
    //作為js中prompt接口的實現,默認需要有一個輸入框一個按鈕,點擊確認按鈕回傳輸入值
    //當然可以添加多個按鈕以及多個輸入框,不過completionHandler只有一個參數,如果有多個輸入框,需要將多個輸入框中的值通過某種方式拼接成一個字符串回傳,js接收到之后再做處理
    //參數 prompt 為 prompt(<message>, <defaultValue>);中的<message>
    //參數defaultText 為 prompt(<message>, <defaultValue>);中的 <defaultValue>
    func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
        let alertViewController = UIAlertController(title: prompt, message: "", preferredStyle: UIAlertController.Style.alert)
        alertViewController.addTextField { (textField) in
            textField.text = defaultText
        }
        alertViewController.addAction(UIAlertAction(title: "完成", style: UIAlertAction.Style.default, handler: { (alertAction) in
            completionHandler(alertViewController.textFields![0].text)
        }))
        self.present(alertViewController, animated: true, completion: nil)
    }
}

 

   參考連接:https://mp.weixin.qq.com/s/Tp7WxHXp_0EATIKvSuBhlw

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM