- 设置
self 为 wkWebView 的 navigationDelegate 代理
override func loadView() {
super.loadView()
view = wkWebView
wkWebView.navigationDelegate = self
}
- 实现
webView:decidePolicyForNavigationAction: decisionHandler 是否加载某个请求
// MARK: - 扩展 ViewController 实现 WKNavigationDelegate 协议
extension ViewController: WKNavigationDelegate {
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
print("request: \(navigationAction.request)")
}
}
- 运行会报错:
Completion handler passed to -[WKWebView.ViewController webView:decidePolicyForNavigationAction:decisionHandler:] was not called
- 也就是说在
webView:decidePolicyForNavigationAction: decisionHandler 代理方法里面必须调用 decisionHandler 这个闭包
decisionHandler方法中传入 Allow -> 同意加载, Cancel -> 取消加载
// MARK: - 扩展 ViewController 实现 WKNavigationDelegate 协议
extension ViewController: WKNavigationDelegate {
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
print("request: \(navigationAction.request)")
// Allow:同意加载, Cancel:取消加载
decisionHandler(.Allow)
}
}
WKWebView 监听 开始加载、加载完成 和 加载失败
// MARK: - 扩展 ViewController 实现 WKNavigationDelegate 协议
extension ViewController: WKNavigationDelegate {
/// 页面开始加载后调用
func webView(webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
print("页面开始加载: \(webView.URL?.absoluteString)")
}
/// 加载完成时调用
func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) {
print("加载完成时调用: \(webView.URL?.absoluteString)")
}
/// 页面加载失败后调用
func webView(webView: WKWebView, didFailNavigation navigation: WKNavigation!, withError error: NSError) {
print("页面加载失败后调用: \(webView.URL?.absoluteString)")
}
/// 在发送请求之前,决定是否加载
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
print("request: \(navigationAction.request)")
// Allow:同意加载, Cancel:取消加载
decisionHandler(.Allow)
}
}
- 修改
wkWebView 的界面,添加 toolBar 来实现 前进、后退、刷新
// MARK: - 懒加载
/// WKWebView
lazy var wkWebView: WKWebView = WKWebView()
/// UIToolbar
private lazy var toolBar: UIToolbar = UIToolbar()
/// 后退item
private lazy var backItem: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Rewind, target: self, action: "back")
/// 前进item
private lazy var forwardItem: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FastForward, target: self, action: "forward")
/// 刷新item
private lazy var refreshItem: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Refresh, target: self, action: "refresh")
- 定义
prepareUI 方法,添加 wkWebView 和 toolBar 到控制器的 view
private func prepareUI() {
self.view.addSubview(wkWebView)
self.view.addSubview(toolBar)
setupToolBar()
wkWebView.translatesAutoresizingMaskIntoConstraints = false
toolBar.translatesAutoresizingMaskIntoConstraints = false
progress.translatesAutoresizingMaskIntoConstraints = false
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[webView]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["webView": wkWebView]))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[toolBar]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["toolBar": toolBar]))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[webView]-0-[toolBar(44)]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["webView": wkWebView, "toolBar": toolBar]))
}
- 在
viewDidLoad 里面调用 prepareUI 方法
- 添加
前进、后退、刷新 这3个 UIBarButtonItem 到 toolBar 里面
/// 设置toolBar
private func setupToolBar() {
backItem.enabled = false
forwardItem.enabled = false
let flexibleF = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: nil, action: nil)
let flexibleS = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: nil, action: nil)
let items = [backItem,flexibleF, forwardItem, flexibleS, refreshItem]
toolBar.items = items
}
// MARK: - 按钮点击事件
/// 回退
func back() {
wkWebView.goBack()
}
/// 前进
func forward() {
wkWebView.goForward()
}
/// 刷新
func refresh() {
let request = NSURLRequest(URL: wkWebView.URL!)
wkWebView.loadRequest(request)
}
- 使用
KVO 来监听 WKWebView 加载状态,判断是否可以 前进、后退
- 监听
WKWebView 的 loading 属性
override func viewDidLoad() {
super.viewDidLoad()
prepareUI()
// 允许手势左划回退到上一个界面
wkWebView.allowsBackForwardNavigationGestures = true
wkWebView.navigationDelegate = self
// 监听 webView加载状态
wkWebView.addObserver(self, forKeyPath: "loading", options: NSKeyValueObservingOptions.New, context: nil)
let url = NSURL(string: "http:image.baidu.com")!
let request = NSURLRequest(URL: url)
wkWebView.loadRequest(request)
}
- 实现监听
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if keyPath == "loading" {
backItem.enabled = wkWebView.canGoBack
forwardItem.enabled = wkWebView.canGoForward
}
}
- 效果如下:

- 到此
WKWebView 的 前进、后退、刷新 功能就实现了
- 实现
WKWebView 加载进度条
- 懒加载
进度条
/// 进度条
private lazy var progress: UIProgressView = UIProgressView(progressViewStyle: UIProgressViewStyle.Bar)
- 在
prepareUI 添加 进度条 为控制器 view 的子控件,并添加约束,让进度条在 toolBar上面
private func prepareUI() {
self.view.addSubview(wkWebView)
self.view.addSubview(toolBar)
self.view.addSubview(progress)
progress.tintColor = UIColor.orangeColor()
setupToolBar()
wkWebView.translatesAutoresizingMaskIntoConstraints = false
toolBar.translatesAutoresizingMaskIntoConstraints = false
progress.translatesAutoresizingMaskIntoConstraints = false
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[webView]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["webView": wkWebView]))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[toolBar]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["toolBar": toolBar]))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[progress]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["progress": progress]))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[webView]-0-[progress(2)]-0-[toolBar(44)]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["webView": wkWebView, "toolBar": toolBar, "progress": progress]))
}
- 在
viewDidLoad 监听 WKWebView 加载进度 estimatedProgress 属性的改变
wkWebView.addObserver(self, forKeyPath: "estimatedProgress", options: .New, context: nil)
- 在
observeValueForKeyPath 里面设置进度条的进度
/// KVO监听方法
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
// 加载状态
if keyPath == "loading" {
backItem.enabled = wkWebView.canGoBack
forwardItem.enabled = wkWebView.canGoForward
}
// 加载进度
if (keyPath == "estimatedProgress") {
progress.hidden = wkWebView.estimatedProgress == 1
progress.setProgress(Float(wkWebView.estimatedProgress), animated: false)
}
}
- 效果如下:

- 在
toolBar 添加 alertItem 来执行 JS 的 alert 函数, 通过使用 WKWebView 的 evaluateJavaScript 来调用 JS 代码
/// 执行js alert
func runAlert() {
wkWebView.evaluateJavaScript("alert('js弹出来的警告框')") { (obj, error) -> Void in
print("obj: \(obj), error: \(error)")
}
}
- 在
WKWebView 中调用 js 的 alert 不会弹出提示框.需要设置 WKWebView 的 UIDelegate, 在 UIDelegate 的 webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler 自己来弹出警告框
- 设置
wkWebView.UIDelegate = self, 让 ViewController 实现 WKUIDelegate 协议
// MARK: - 扩展 ViewController 实现 WKUIDelegate 协议
extension ViewController: WKUIDelegate {
func webView(webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: () -> Void) {
}
}
- 当实现
UIDelegate 的 webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler 而没有调用 completionHandler 时系统会报错:Completion handler passed to -[WKWebView.ViewController webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler:] was not called
// MARK: - 扩展 ViewController 实现 WKUIDelegate 协议
extension ViewController: WKUIDelegate {
/// Web界面中有弹出警告框时调用
func webView(webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: () -> Void) {
// 必须调用,否则报错
completionHandler()
// WKWebView不会自己调用系统的AlertView,需要我们自己来调用AlertController弹框
let alert = UIAlertController(title: "js 调用alert, iOS拦截到,弹出UIAlertController", message: "\(message)", preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "ok", style: .Default, handler:nil))
alert.addAction(UIAlertAction(title: "cancel", style: .Cancel, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
}
- 在
ViewController 里面懒加载 2个 UIBarButtonItem, 当点击的时候让 JS 调用本地代码
/// JSCallNative item
private lazy var JSPrintMessageItem: UIBarButtonItem = UIBarButtonItem(title: "JSPrintMessage", style: UIBarButtonItemStyle.Plain, target: self, action: "JSPrintMessage")
/// JSCallNative item
private lazy var JSModalVCItem: UIBarButtonItem = UIBarButtonItem(title: "JSModalVC", style: UIBarButtonItemStyle.Plain, target: self, action: "JSModalVC")
- 将这2个
UIBarButtonItem 添加到 toolBar 上,效果如下:
WKWebView 里面处理 JS 调用 本地代码 的方式和 UIWebView 不一样. UIWebView 是通过 拦截请求 的方式. WKWebView 是在 WKWebView 初始化时 addScriptMessageHandler(self, name: "callNative"), JS 中执行 window.webkit.messageHandlers.callNative(用户自定义).postMessage(message); message一般为字典,可以传入多个值, WKWebView 的 WKScriptMessageHandler 代理的 userContentController:didReceiveScriptMessage: 方法会被调用.在这个方法里面处理 JS 传来的值
- 修改
WKWebView 懒加载代码,在懒加载添加 WKWebViewConfiguration 并设置 addScriptMessageHandler
// MARK: - 懒加载
/// WKWebView
private lazy var wkWebView: WKWebView = {
let config = WKWebViewConfiguration()
/// 设置 JS中调用本地代码的方法为: callNative,WKWebView会回调 WKScriptMessageHandler 协议的 userContentController: WKUserContentController:didReceiveScriptMessage: 方法
config.userContentController.addScriptMessageHandler(self, name: "callNative")
let webView = WKWebView(frame: CGRectZero, configuration: config)
return webView
}()
- 由于我们现在加载的是
http:image.baidu.com, 这个 HTML 里面没有我们自己写好的 JS 代码.可以在页面加载完成的时候来注入 JS 代码
- 准备
JS 代码,放在 index.js, 并添加到工程
function JSPrintMessage() {
var message = {
'method' : 'printMessage',
'param1' : 'js call swift',
};
window.webkit.messageHandlers.callNative.postMessage(message);
}
//
function JSModalVC() {
var message = {
'method' : 'modalVC',
'param1' : 'TestViewController',
};
window.webkit.messageHandlers.callNative.postMessage(message);
}
- 将
index.js 文件里面的 JS 代码加载到 ViewController 里面来
/// js代码
private lazy var JSCode: String = {
let url = NSBundle.mainBundle().URLForResource("index.js", withExtension: nil)
return try! String(contentsOfURL: url!, encoding: NSUTF8StringEncoding)
}()
- 在
WKWebView 加载完成回调的 WKNavigationDelegate 的 webView:didFinishNavigation: 方法中来注入我们刚才写好的 JS 代码
/// 网页加载完成时调用
func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) {
print("加载完成时调用: \(webView.URL?.absoluteString)")
// 注入js代码
webView.evaluateJavaScript(JSCode) { (result, error) -> Void in
// 检查 JS 代码中是否有 JSPrintMessage 这个函数,若打印 function 表示注入成功
let jsCheck = "typeof JSPrintMessage;"
webView.evaluateJavaScript(jsCheck, completionHandler: { (result, error) -> Void in
if error == nil && (result as? String) == "function" {
print("注入js代码成功")
} else {
print("注入js代码失败")
}
})
}
}
- 扩展
ViewController 实现 WKScriptMessageHandler 协议
// MARK: - 扩展 ViewController 实现 WKScriptMessageHandler 协议
extension ViewController: WKScriptMessageHandler {
/// 当 JS 中调用 window.webkit.messageHandlers.callNative.postMessage(message); 方法就会执行这个方法
/// 其中callNative 为自定义,需要在WKWebView注册
func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {
// 获取JS传过来的参数
let dict = message.body as! [String: String]
// 获取JS要调用的方法名称
let method = dict["method"]
// 根据不同方法
if method == "printMessage" {
let message = dict["param1"]!
printMessage(message)
} else if (method == "modalVC") {
let message = dict["param1"]!
modalVC(message)
}
}
/// JS 调用本地方法,控制台输出
func printMessage(message: String) {
print("printMessage: \(message)")
}
/// JS 调用本地方法,Modal出新的控制器
func modalVC(vcName: String) {
// Swift的类名需要使用: ProductName.ClassName,本项目ProductName为WKWebView
let cls = NSClassFromString("WKWebView." + vcName) as? UIViewController.Type
let vc = cls?.init()
self.presentViewController(UINavigationController(rootViewController: vc!), animated: true, completion: nil)
}
}
- 当点击
toolBar 上面的 JSPrintMessage Item 时调用以下代码
/// Swift 执行 JS 的 JSPrintMessage() 方法, JS调用Native(Swift/OC)
func JSPrintMessage() {
wkWebView.evaluateJavaScript("JSPrintMessage()") { (result, error) -> Void in
}
}
- 在控制台可以打印JS传过来的值:
printMessage: js call swift
- 当点击
toolBar 上面的 JSModalVC Item 时调用以下代码
/// Swift 执行 JS 的 JSModalVC() 方法,JS调用Native(Swift/OC)
func JSModalVC() {
wkWebView.evaluateJavaScript("JSModalVC()") { (result, error) -> Void in
}
}
- 最终会调用
modalVC 方法,Modal出新的控制器
/// JS 调用本地方法,Modal出新的控制器
func modalVC(vcName: String) {
// Swift的类名需要使用: ProductName.ClassName,本项目ProductName为WKWebView
let cls = NSClassFromString("WKWebView." + vcName) as? UIViewController.Type
let vc = cls?.init()
self.presentViewController(UINavigationController(rootViewController: vc!), animated: true, completion: nil)
}