iOS_WKWebView与JS交互 Demo

2022-07-20 14:11:29 浏览数 (1)

一、WKWebView的使用:

1、初始化

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

这里添加了三个代理,代理方法会在下面实现。

代码语言:javascript复制
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 (不需要的可以跳过)

代码语言:javascript复制
// 加载进度条
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`时显示`向后`按钮;

代码语言:javascript复制
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

代码语言:javascript复制
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`进行响应的处理,否则报错。

代码语言:javascript复制
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:

代码语言:javascript复制
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来分别处理不同类型的消息。

代码语言:javascript复制
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那边的实现

代码语言:javascript复制
<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交互了:

代码语言:javascript复制
  // 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的方法/获取信息

代码语言:javascript复制
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文件的代码:

代码语言:javascript复制
<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>

还有些代理方法没有实现,没有研究,待续吧~

github Demo 地址

参考1、参考2

0 人点赞