# 了解WKWebView

> 当前环境： Xcode10.0 Swift4.2 iOS SDK 12.1

Demo: <https://github.com/zColdWater/wkwebviewdemo>

## 关于 WKWebView

> [官方说明](https://developer.apple.com/documentation/webkit/wkwebview): 从 iOS 8.0 和 OS X 10.10 开始，使用 WKWebView 将 Web 内容添加到您的应用程序。不要使用 UIWebView 或 WebView。
>
> 自诩拥有 60fps 滚动刷新率、内置手势、高效的 app 和 Web 信息交换通道、和 Safari 相同的 JavaScript 引擎，WKWebView 毫无疑问地成为了 WWDC 2014 上的最亮点。

1. 在应用程序的主要进程之外运行

   > WKWebView 用完了进程，这意味着它的内存与应用程序分开进行线程化; 当它超出其分配时，它将崩溃而不会崩溃应用程序（这会导致应用程序被通知并尝试重新加载页面）。 相比之下，UIWebView 正在运行，这意味着它使用的内存被认为是应用程序占用空间的一部分，如果这超出了 iOS 想要分配的内容，应用程序本身将被操作系统崩溃。虽然在发生这种情况之前经常会有来自 iOS 的通知，这可以让我们避免崩溃，但有时这些通知不会很快返回，或者根本不会返回。
2. 使用 Nitro 一个更快的 JavaScript 引擎

   > WKWebView 使用也被移动 Safari 使用的 Nitro JavaScript 引擎，与 UIWebView 的 JavaScript 引擎相比，它具有显着的性能改进。
3. 消除了某些触摸延迟

   > UIWebView 和 WKWebView 浏览器组件解释并将触摸事件传递给应用程序。因此，我们无法提高触摸事件的灵敏度或速度。 触摸任何内容后， UIWebView 会 延迟 300ms，以确定用户是单击还是双击。这种延迟是许多用户认为基于 HTML 的网络应用程序“迟钝”的最主要原因之一。在 WKWebView 中，测试显示 300ms 延迟仅在快速点击（<\~125 ms）后添加，iOS 解释为更有可能成为双击“点击缩放”手势的一部分，而不是慢速敲击后（> \~125 ms）。

## WKWebView 基本方法

```swift
// 加载请求
1.webview.load()  
// 网页到上一页
2.webview.goBack()  
// 网页到下一页
3.webview.goForward()  
// 网页重新加载
4.webview.reload()  
// 网页停止加载
5.webview.stopLoading()  
// 网页标题
6.webview.title  
// 网页能够后退到上一页
7.webview.canGoBack  
// 网页能够前进到下一页
8.webview.canGoForward  
// 网页加载当中的进度
9.webview.estimatedProgress
```

## WKNavigationDelegate 协议

> [官方文档](https://developer.apple.com/documentation/webkit/wknavigationdelegate) 协议的方法可帮助您实现在 Web 视图接受，加载和完成导航请求的过程中触发的自定义行为。WKNavigationDelegate

```swift
    // MARK: - WKNavigationDelegate

    // 【方法一】
    // 决定网页是否允许跳转。
    // WebView里面的每一次请求都会被拦截。
    // 然后通过 decisionHandler 回调参数 来决定 允许 或者 不允许。
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        if let host = navigationAction.request.url?.host {
            if host == "zcoldwater.github.io" {
                // 不允许
                decisionHandler(WKNavigationActionPolicy.cancel)
                return
            }
        }
        // 允许
        decisionHandler(WKNavigationActionPolicy.allow)
    }

    // 【方法二】
    // 收到网页 Response 决定是否跳转
    // 1. 先经过【方法一】再经过【方法二】
    func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {

        // 默认回调参数 allow 允许跳转
        decisionHandler(WKNavigationResponsePolicy.allow)
    }

    // 【方法三】
    // 页面内容开始加载
    func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
        print("页面内容正在开始加载！")
    }

    // 【方法四】
    // 网页内容加载失败
    func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
        print("网页内容加载失败！")
    }

    // 【方法五】
    // 网页内容加载完成后，返回内容至 webview
    func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
        print("网页内容加载完成后，返回内容至 webview")
    }

    // 【方法六】
    // 网页加载完成
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        print("网页加载完成!")
    }

    // 【方法七】
    // 网页返回内容至 webview 时发生失败
    func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
        print("网页返回内容至 webview 时发生失败!")
    }

    // 【方法八】
    // 收到网页重新定向的请求
    func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) {
        print("收到页面重定向请求!")
    }

    // 【方法九】
    // 处理网页过程中发生终止
    func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
        print("处理网页过程中发生终止\n 内存占用过大等原因导致的系统调用此方法!")
    }
```

## WKUIDelegate 协议【JavaScript -> Native】

> Web 视图用户界面委托实现此协议以控制新窗口的打开，增强用户单击元素时显示的默认菜单项的行为，以及执行其他与用户界面相关的任务。可以在处理 JavaScript 或其他插件内容时调用这些方法。默认 Web 视图实现假定每个 Web 视图有一个窗口，因此非传统用户界面可能实现用户界面委托。

```swift
    // 创建新的webView时调用的方法
    // 内部实现根据实际需求写
    func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
        return nil
    }

    // 关闭webView时调用的方法
    // 内部实现根据实际需求写
    func webViewDidClose(_ webView: WKWebView) {
        print("webViewDidClose:\(webView)")
    }

    // 警告框
    // Javascript中调用 alert("any") 触发
    func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
    }

    // 确认框    
    // Javascript中调用 confirm("any") 触发
    func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
    }

    // 输入框
    // Javascript中调用 prompt("param1", "param2")
    func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
    }

    // 能否预览用户触摸的元素
    // 根据需要实现下面协议
    func webView(_ webView: WKWebView, shouldPreviewElement elementInfo: WKPreviewElementInfo) -> Bool {
        return true
    }
```

## WKWebView 必要方法【Native -> JavaScript】

> 如何我们要实现 Native 调用 JavaScript 的时候就必须要知道下面这个方法了

```swift
    /* @abstract Evaluates the given JavaScript string.
     @param javaScriptString The JavaScript string to evaluate.
     @param completionHandler A block to invoke when script evaluation completes or fails.
     @discussion The completionHandler is passed the result of the script evaluation or an error.
    */
    open func evaluateJavaScript(_ javaScriptString: String, completionHandler: ((Any?, Error?) -> Void)? = nil)
```

## 补充

1. 打开本地沙盒目录下的 HTML 如何打开？

   ```swift
   // 使用下面的方法来加载本地沙盒下的本地路径 
   // 这里有个小贴士，第一个参数是本地html的地址，第二个参数是可以访问的范围地址，一般要写前面html的根路径，因为有些时候我们的本地不光有html还有一些css/js包也在同一个目录下。
   webview.loadFileURL(<#T##URL: URL##URL#>, allowingReadAccessTo: <#T##URL#>)
   ```

## 总结

我们了解 `WKWebView` 的几个普遍的方法，和几个常用的协议，这时候我们会发现，其实 Apple 已经给我们提供了 JavaScript 与 Native 的桥梁，通过 `evaluateJavaScript [Native -> Javascript]`, `WKUIDelegate [Javascript->Native]`，通过这两种方式 我们可以获得，Native 与 JS 互相通信的能力。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://yongpenglovemimi123.gitbook.io/henry/ios/le-jie-wkwebview.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
