iOS – WKWebView笔记(3) — WKUIDelegate | WKNavigationDelegate

WKWebView笔记 — WKUIDelegate | WKNavigationDelegate

WKUIDelegate

从名字上看,这个类和UI有关。在第一次使用WKWebView的时候,发现在web中写的alert没有响应。后来查资料只,web中的alert,confirm,prompt等弹窗都被WKWebView拦截,WKUIDelegate 提供了三个方法来代替JS的弹窗。

// alert
optional public func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void)

// confirm
optional public func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void)

// prompt
optional public func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void)

除了上述方法,还包含两个方法

// 当 webView 被关闭时调用。
optional public func webViewDidClose(_ webView: WKWebView)

// 创建新的webView
// 可以指定配置对象、导航动作对象、window特性。如果没用实现这个方法,不会加载链接,如果返回的是原webview会崩溃。
// 具体用途在下面分析
optional public func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView?

在使用WKUIDelegate时,需要设置 webView.uiDelegate = self

可以使用如下代码模拟注入js代码

webView.evaluateJavaScript("alert('hello')", completionHandler: nil)

alert弹出案例

JS代码

alert("Hello World!");

Swift

func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
    let alertVC = UIAlertController(title: "Alert", message: message, preferredStyle: .alert)
    
    let cancelBtn = UIAlertAction(title: "好的", style: .default) { (_) in
        completionHandler()
    }
    
    alertVC.addAction(cancelBtn)
    
    present(alertVC, animated: true, completion: nil)
}

confirm 案例, 可以回传false/true

JS代码

let confirmDel = confirm("确定删除?");

Swift

func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
    let alertVC = UIAlertController(title: "Confirm", message: message, preferredStyle: .alert)
    
    let confirmBtn = UIAlertAction(title: "确定", style: .default) { (_) in
        completionHandler(true)
    }
    alertVC.addAction(confirmBtn)
    
    let cancelBtn = UIAlertAction(title: "取消", style: .destructive) { (_) in
        completionHandler(false)
    }
    alertVC.addAction(cancelBtn)
    
    present(alertVC, animated: true, completion: nil)
}

Prompt案例,可以回传输入的值

JS

let name = prompt("报名", "请输入姓名");

Swift

func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
    let alertVC = UIAlertController(title: "Prompt", message: prompt, preferredStyle: .alert)
    alertVC.addTextField { (textField) in
        textField.placeholder = defaultText
    }
    
    let confirmBtn = UIAlertAction(title: "确定", style: .default) { (_) in
        completionHandler(alertVC.textFields?.first?.text ?? "")
    }
    alertVC.addAction(confirmBtn)
    
    present(alertVC, animated: true, completion: nil)
}

WKNavigationDelegate

第一次看到这类的时候,觉得这个类会和NavigationController一样控制页面的push和pop。而仔细阅读之后发现和页面的加载有关,类似是监听网页的生命周期。

// 网页加载之前调用。例如可以在网页加载之前判断url是否处在黑名单,然后取消加载
// WKNavigationActionPolicy有两个值,cancel/allow, 通过此回调决定是否继续加载。
optional public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void)

// 收到响应后,决定是否跳转
optional public func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void)

// 页面开始加载
optional public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!)

// 页面重定向
optional public func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!)

// 页面加载失败
optional public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error)

// 内容接收时调用
optional public func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!)

// (页面加载完成)内容接收完成时调用
optional public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)

// 内容接收失败
optional public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error)

// 证书验证
optional public func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)

// 处理中断时调用
optional public func webViewWebContentProcessDidTerminate(_ webView: WKWebView)

WKNavigationAction

从这个类中我觉得WKNavigationDelegate和NavigationController还是有些许相似的。

WKNavigationAction 属性

// 请求跳转的frame
@NSCopying open var sourceFrame: WKFrameInfo { get }

// 目标frame
@NSCopying open var targetFrame: WKFrameInfo? { get }

// 跳转类型
// linkActivated    点击链接
// formSubmitted    提交表单
// backForward      前进或者返回
// reload           重新加载
// formResubmitted  重新提交表单
// other            其他原因
open var navigationType: WKNavigationType { get }
open var request: URLRequest { get }

点击 target="__blank" 的链接无反应的问题。

在上一篇笔记中夜间模式的demo中,我发现点击juejin.im的文章链接是无响应。后来发现这些链接是需要从新页面打开的。 第一反应是其他浏览器都会通过新的卡片去打开页面。

Chrome桌面版

Safari卡片

Alook卡片

因为webView只是其中一个卡片,所以当需要打开新的页面时,需要添加一个webView.

每次点击URL的时候,会首先调用 WKNavigationDelegatedecidePolicyFor navigationAction: WKNavigationAction, decisionHandler: 方法,navigationAction包含sourceFrame和targetFrame,如果需要打开新的窗口时,targetFrame为nil。此时可以做一些操作。

当然就算不在此代理方法做处理,当需要打开新的页面时,最终也会自动调用 WKUIDelegatewebView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) 方法, 此方法需要返回一个新建的webView。新页面的跳转需要自己实现,并不是说返回一个新的webView就可以的。

func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
    let newWebView = WKWebView(frame: webView.frame, configuration: configuration)
    newWebView.load(navigationAction.request)
    newWebView.uiDelegate = self
    newWebView.navigationDelegate = self
    
    let vc = UIViewController()
    vc.view.addSubview(newWebView)
    
    navigationController?.pushViewController(vc, animated: true)
    
    return newWebView
}

最终效果