iOS_WKWebView與JS交互 Demo

一、WKWebView的使用:

1、初始化

webView初始化:WKPreferences, WKUserContentController -> WKWebViewConfiguration -> WKWebView

這裏添加了三個代理,代理方法會在下面實現。

let preferences = WKPreferences()
preferences.javaScriptEnabled = true
preferences.javaScriptCanOpenWindowsAutomatically = true // default value is NO in iOS and YES in OS X.
let configuration = WKWebViewConfiguration()
configuration.preferences = preferences;
configuration.userContentController = WKUserContentController()
// 註冊JS發送消息的name (自定義,可以設置多個)
configuration.userContentController.add(self, name: "moxiaoyan") // 添加 WKScriptMessageHandler 代理
let webView = WKWebView(frame: CGRect(x: 0, y: (navigationController?.navigationBar.frame.maxY ?? 0) + 1, width: view.frame.size.width, height: view.frame.size.height/2), configuration: configuration)
webView.navigationDelegate = self // 添加 WKNavigationDelegate 代理
webView.uiDelegate = self // 添加 WKUIDelegate 代理

進度條:顯示頁面加載進度,加載完成後 hidden (不需要的可以跳過)

// 加載進度條
progressView = UIProgressView(progressViewStyle: .default)
progressView.progressTintColor = .green
progressView.trackTintColor = .lightGray
progressView.progress = 0.0
progressView.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: 1)
webView.addSubview(progressView)
// 添加觀察者
webView.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
  if keyPath == "estimatedProgress" {
    progressView.setProgress(Float(webView.estimatedProgress), animated: true)
    if webView.estimatedProgress >= 1.0 {
      progressView.isHidden = true
    }
  }
}

向前<、向後>、關閉X 按鈕實現 (不需要的可以跳過)

當`webView`可以`goForward`時顯示`向前`按鈕;

當`webView`可以`goBack`時顯示`向後`按鈕;

lazy var forwardBtnItem: UIBarButtonItem = {
  let btn = UIBarButtonItem(image: UIImage(named: "mo_green_forward"), style: .done, target: self, action: #selector(goForward))
  btn.imageInsets = UIEdgeInsets(top: 0, left: -30, bottom: 0, right: 30)
  return btn
}()
private lazy var closeBtnItem: UIBarButtonItem = {
  let btn = UIBarButtonItem(image: UIImage(named: "mo_green_close"), style: .done, target: self, action: #selector(close))
  btn.imageInsets = UIEdgeInsets(top: 0, left: -34, bottom: 0, right: 34)
  return btn
}()

// viewDidLoad方法裏:
navigationItem.leftBarButtonItem = backBtnItem

// WKNavigationDelegate 的代理方法
// MARK: - 加載完成
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
  print("加載完成: didFinish")
  self.title = webView.title
  var btns = [backBtnItem]
  if webView.canGoForward {
    btns.append(forwardBtnItem)
  }
  if webView.canGoBack {
    btns.append(closeBtnItem)
  }
  navigationItem.leftBarButtonItems = btns
}

最後是load

let request = URLRequest(url: self.url)
webView.load(request)

// 不能用 webView.loadHTMLString(self.url.absoluteString, baseURL: self.url) 
// 否則evaluateJavaScript方法報錯

2、WKNavigationDelegate:

這個代理在加載的各個過程中都有回調,可以根據項目需求,做響應的處理:

前面三個 `func webView(_ webView: WKWebView, decidePolicyFor ...` 的方法是一樣的,只是帶的參數不一樣,如果實現了,就必須調用`decisionHandler`進行響應的處理,否則報錯。

extension MOWKWebViewController: WKNavigationDelegate {
  // MARK: - 判斷連接是否允許跳轉 navigationAction
  func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    print("判斷連接是否允許跳轉: decidePolicyFor navigationAction: \(navigationAction)")
    decisionHandler(.allow) // .allow or .calcel
  }
  // MARK: - 判斷連接是否允許跳轉 navigationAction preferences:
  func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences, decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void) {
    print("判斷連接是否允許跳轉: decidePolicyFor navigationAction: \(navigationAction) \n preferences: \(preferences)")
    decisionHandler(.allow, preferences) // .allow or .calcel
  }
  // MARK: - 判斷連接是否允許跳轉 navigationResponse
  func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
    print("拿到響應後決定是否跳轉: decidePolicyFor navigationResponse: \(navigationResponse)")
    decisionHandler(.allow) // .allow or .calcel
  }
  // MARK: - 服務器重定向
  func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) {
    print("服務器重定向: didReceiveServerRedirectForProvisionalNavigation")
  }
  // MARK: - 加載完成
  func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    print("加載完成: didFinish")
    self.title = webView.title
  }
  // MARK: - 加載失敗
  func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
    print("加載失敗: didFail error: \(error)")
  }
  // MARK: - 即將完成
  func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
    print("即將完成: didCommit")
  }
  // MARK: - 加載錯誤
  func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
    print("加載錯誤: didFailProvisionalNavigation: \(error)")
  }
  // MARK: - 需要響應身份驗證時調用(需驗證服務器證書)
  func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    print("需驗證服務器證書: didReceive challenge")
  }
  // MARK: - web內容進程被終止時調用(iOS 9.0之後)
  func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
    print("進程被終止: webViewWebContentProcessDidTerminate")
  }
}

3、WKUIDelegate:

一些UI展示和交互需要App這邊來做,交互的結果卻得反饋給JS:

extension MOWKWebViewController: WKUIDelegate {
  func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
    print("alert: msg:\(message)")
    let alertVC = UIAlertController(title: "提示", message:message, preferredStyle: .alert)
    alertVC.addAction(UIAlertAction(title: "確認", style: .default, handler: { (action) in
      completionHandler() // 告知JS結果
    }))
    self.present(alertVC, animated: true, completion: nil)
  }
  func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
    print("confirm: msg:\(message)")
    let alertVC = UIAlertController(title: "提示", message: message, preferredStyle: .alert)
    alertVC.addAction(UIAlertAction(title: "取消", style: .cancel, handler: { (alertAction) in
      completionHandler(false) // 告知JS選擇結果
    }))
    alertVC.addAction(UIAlertAction(title: "確定", style: .default, handler: { (alertAction) in
      completionHandler(true) // 告知JS選擇結果
    }))
    self.present(alertVC, animated: true, completion: nil)
  }
  func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
    print("input: prompt:\(prompt) defaultText:\(String(describing: defaultText))")
    let alertVC = UIAlertController(title: prompt, message: "", preferredStyle: .alert)
    alertVC.addTextField { (textField) in
      textField.text = defaultText
    }
    alertVC.addAction(UIAlertAction(title: "完成", style: .default, handler: { (alertAction) in
      completionHandler(alertVC.textFields![0].text) // 告知JS結果
    }))
    self.present(alertVC, animated: true, completion: nil)
  }
}

4、WKScriptMessageHandler:

接收JS那邊發送過來得消息,消息有name ,是在我們初始化( configuration.userContentController.add(self, name: "moxiaoyan") )設置的。可以根據name來分別處理不同類型的消息。

extension MOWKWebViewController: WKScriptMessageHandler {
  func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    // JS:window.webkit.messageHandlers.moxiaoyan.postMessage
    print("didReceive message: \(message.body)")
    switch message.name {
    case "moxiaoyan":
      print("收到發給我的消息啦: \(message.body)")
    default: break
    }
  }
}

這邊貼一下JS那邊的實現

<script type="text/javascript">
    // 向APP傳遞數據
    function buttonAction() {
      try {
        window.webkit.messageHandlers.moxiaoyan.postMessage("JS: 客戶下單啦~");
      } catch (e) {
        console.log(e);
      }
    }
</script>

 

二、與JS的交互

其實上面的代理實現的已經差不多了,下面補充並說明一下:

1、evaluateJavaScript的使用

1)、首先我們在`WKNavigationDelegate`的`didFinish`方法回調後,就可以使用`evaluateJavaScript`跟JS交互了:

  // MARK: - 加載完成
  func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    print("加載完成: didFinish")
    // 調用js方法(把標題h1設置成紅色)
    webView.evaluateJavaScript("changeHead()", completionHandler: { (data, error) in
      print("changeHead data:\(String(describing: data)) error: \(String(describing: error))")
    })
  }

    我在這卡了很久,一直報錯:
    Error Domain=WKErrorDomain Code=4 "A JavaScript exception occurred" UserInfo={WKJavaScriptExceptionLineNumber=1, WKJavaScriptExceptionMessage=ReferenceError: Can't find variable: changeHead, WKJavaScriptExceptionColumnNumber=11,...
     是因爲加載網頁的方法用的不對:
     我用了: webView.loadHTMLString(_ string: String, baseURL: URL?) -> WKNavigation?
     應該用: webView.load(_ request: URLRequest) -> WKNavigation?

2)、這裏在app的原生頁面寫了兩個button調用JS的方法/獲取信息

func setupButtons() {
  let baseHeight = (navigationController?.navigationBar.frame.maxY ?? 0) + 20;
  let getJSBtn = UIButton(type: .custom)
  getJSBtn.setTitle("獲取JS信息", for: .normal)
  getJSBtn.setTitleColor(.red, for: .normal)
  getJSBtn.addTarget(self, action: #selector(getJSInformation), for: .touchUpInside)
  getJSBtn.frame = CGRect(x: 30, y: view.frame.size.height/2 + baseHeight, width: 120, height: 40)
  view.addSubview(getJSBtn)
  
  let callJSFuncBtn = UIButton(type: .custom)
  callJSFuncBtn.setTitle("調用JS方法", for: .normal)
  callJSFuncBtn.setTitleColor(.red, for: .normal)
  callJSFuncBtn.addTarget(self, action: #selector(callJSFunc), for: .touchUpInside)
  callJSFuncBtn.frame = CGRect(x: 200, y: view.frame.size.height/2 + baseHeight, width: 120, height: 40)
  view.addSubview(callJSFuncBtn)
}
// MARK: - 獲取JS title,並打印
@objc func getJSInformation() {
  let js = "document.getElementsByTagName('h1')[0].innerText";
  webView.evaluateJavaScript(js, completionHandler: { (data, error) in
    print("getJSInformation data:\(String(describing: data)) error: \(String(describing: error))")
  })
}
// MARK: - 調用JS的方法,並打印返回數據
@objc func callJSFunc() {
  webView.evaluateJavaScript("swiftTestObject('xjf', 26)", completionHandler: { (data, error) in
    print("callJSFunc data:\(String(describing: data)) error: \(String(describing: error))")
  })
}

下面是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 APP彈框</button>
        <button onclick="testConfrim()">點擊Confrim APP彈窗</button>
        <button onclick="testPrompt('你是誰???')">點擊Prompt APP彈窗</button>
        <button onclick="buttonAction()">向APP傳遞數據</button>
        <script type="text/javascript">
            // APP調用js
            function changeHead(){
              document.querySelector('h1').style.color = "red"
            }
            // 接受APP傳過來的參數
            function swiftTestObject(name, age) {
              var object = {name:name, age:age};
              return object;
            }
            function testConfrim() {
              confirm("確定修改數據嗎?")
            }
            function testPrompt(title) {
              prompt(title, "莫小言")
            }
            // 無參數函數
            function testA() {
              alert("我是JS中的彈窗消息");
            }
            // 向APP傳遞數據
            function buttonAction() {
              try {
                window.webkit.messageHandlers.moxiaoyan.postMessage("JS: 客戶下單啦~");
              } catch (e) {
                console.log(e);
              }
            }
        </script>
    </body>
</html>

 

還有些代理方法沒有實現,沒有研究,待續吧~

Demo下載地址

參考1參考2

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章