引言

一直听说WKWebViewUIWebView强大许多,可是一直没有使用到,今天花了点时间看写了个例子,对其API的使用有所了解,为了日后能少走弯路,也为了让大家更容易学习上手,这里写下这篇文章来记录如何使用以及需要注意的地方。

温馨提示:本人在学习使用过程中,确实有此体会,WKWebView的确比UIWebView强大很多,与JS交互的能力显示增强,在加载速度上有所提升。

WKWebView新特性

  • 性能、稳定性、功能大幅度提升
  • 允许JavaScript的Nitro库加载并使用(UIWebView中限制)
  • 支持了更多的HTML5特性
  • 高达60fps的滚动刷新率以及内置手势
  • GPU硬件加速
  • KVO
  • 重构UIWebView成14类与3个协议,查看官方文档

准备工作

首先,我们在使用的地方引入模块:

 
1
2
3
 
import Webkit
 

在学习之前,建议大家先点击WKWebView进去阅读里面的相关API,读完一遍,有个大概的印象,学习起来就很快了。

其初始化方法有:

 
1
2
3
4
5
 
public init()
public init(frame: CGRect)
public init(frame: CGRect, configuration: WKWebViewConfiguration)
 

加载HTML的方式与UIWebView的方式大致相同。其中,loadFileURL方法通常用于加载服务器的HTML页面或者JS,而loadHTMLString方法通常用于加载本地HTML或者JS:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
public func loadRequest(request: NSURLRequest) -> WKNavigation?
 
// 9.0以后才支持  
@available(iOS 9.0, *)
public func loadFileURL(URL: NSURL, allowingReadAccessToURL readAccessURL: NSURL) -> WKNavigation?
 
// 通常用于加载本地HTML或者JS
public func loadHTMLString(string: String, baseURL: NSURL?) -> WKNavigation?
    
// 9.0以后才支持
@available(iOS 9.0, *)
public func loadData(data: NSData, MIMEType: String, characterEncodingName: String, baseURL: NSURL) -> WKNavigation
 

与之交互用到的三大代理:

  • WKNavigationDelegate,与页面导航加载相关
  • WKUIDelegate,与JS交互时的ui展示相关,比较JS的alert、confirm、prompt
  • WKScriptMessageHandler,与js交互相关,通常是ios端注入名称,js端通过window.webkit.messageHandlers.{NAME}.postMessage()来发消息到ios端

创建WKWebView

首先,我们在ViewController中先遵守协议:

 
1
2
3
 
class ViewController: UIViewController, WKScriptMessageHandler, WKNavigationDelegate, WKUIDelegate
 

我们可以先创建一个WKWebView的配置项WKWebViewConfiguration,这可以设置偏好设置,与网页交互的配置,注入对象,注入js等:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 
// 创建一个webiview的配置项
let configuretion = WKWebViewConfiguration()
    
// Webview的偏好设置
configuretion.preferences = WKPreferences()
configuretion.preferences.minimumFontSize = 10
configuretion.preferences.javaScriptEnabled = true
// 默认是不能通过JS自动打开窗口的,必须通过用户交互才能打开
configuretion.preferences.javaScriptCanOpenWindowsAutomatically = false
    
// 通过js与webview内容交互配置
configuretion.userContentController = WKUserContentController()
    
// 添加一个JS到HTML中,这样就可以直接在JS中调用我们添加的JS方法
let script = WKUserScript(source: "function showAlert() { alert('在载入webview时通过Swift注入的JS方法'); }",
  injectionTime: .AtDocumentStart,// 在载入时就添加JS
  forMainFrameOnly: true) // 只添加到mainFrame中
configuretion.userContentController.addUserScript(script)
    
// 添加一个名称,就可以在JS通过这个名称发送消息:
// window.webkit.messageHandlers.AppModel.postMessage({body: 'xxx'})
configuretion.userContentController.addScriptMessageHandler(self, name: "AppModel")
 

创建对象并遵守代理:

 
1
2
3
4
5
6
 
self.webView = WKWebView(frame: self.view.bounds, configuration: configuretion)
 
self.webView.navigationDelegate = self
self.webView.UIDelegate = self
 

加载我们的本地HTML页面:

 
1
2
3
4
5
 
let url = NSBundle.mainBundle().URLForResource("test", withExtension: "html")
self.webView.loadRequest(NSURLRequest(URL: url!))
self.view.addSubview(self.webView);
 

我们再添加前进、后退按钮和添加一个加载进度的控制显示在Webview上:

 
1
2
3
4
5
6
7
8
9
 
self.progressView = UIProgressView(progressViewStyle: .Default)
self.progressView.frame.size.width = self.view.frame.size.width
self.progressView.backgroundColor = UIColor.redColor()
self.view.addSubview(self.progressView)
    
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "上一个页面", style: .Done, target: self, action: "previousPage")
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "下一个页面", style: .Done, target: self, action: "nextPage")
 

页面前进、后退

对于前进后退的事件处理就很简单的,要注意判断一下是否可以后退、前进才调用:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
 
func previousPage() {
    if self.webView.canGoBack {
      self.webView.goBack()
    }
}
  
func nextPage() {
    if self.webView.canGoForward {
      self.webView.goForward()
    }
}
 

当然除了这些方法之处,还有重新载入等。

WKWebView的KVO

对于WKWebView,有三个属性支持KVO,因此我们可以监听其值的变化,分别是:loading,title,estimatedProgress,对应功能表示为:是否正在加载中,页面的标题,页面内容加载的进度(值为0.0~1.0)

 
1
2
3
4
5
6
 
// 监听支持KVO的属性
self.webView.addObserver(self, forKeyPath: "loading", options: .New, context: nil)
self.webView.addObserver(self, forKeyPath: "title", options: .New, context: nil)
self.webView.addObserver(self, forKeyPath: "estimatedProgress", options: .New, context: nil)
 

然后就可以重写监听的方法来处理。这里只是取页面的标题,更新加载的进度条,在加载完成时,手动调用执行一个JS方法:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 
// MARK: - KVO
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
  if keyPath == "loading" {
    print("loading")
  } else if keyPath == "title" {
    self.title = self.webView.title
  } else if keyPath == "estimatedProgress" {
    print(webView.estimatedProgress)
    self.progressView.setProgress(Float(webView.estimatedProgress), animated: true)
  }
  
  // 已经完成加载时,我们就可以做我们的事了
  if !webView.loading {
    // 手动调用JS代码
    let js = "callJsAlert()";
    self.webView.evaluateJavaScript(js) { (_, _) -> Void in
      print("call js alert")
    }
    
    UIView.animateWithDuration(0.55, animations: { () -> Void in
      self.progressView.alpha = 0.0;
    })
  }
}
 

WKUIDelegate

我们看看WKUIDelegate的几个代理方法,虽然不是必须实现的,但是如果我们的页面中有调用了js的alert、confirm、prompt方法,我们应该实现下面这几个代理方法,然后在原来这里调用native的弹出窗,因为使用WKWebView后,HTML中的alert、confirm、prompt方法调用是不会再弹出窗口了,只是转化成ios的native回调代理方法:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
 
// MARK: - WKUIDelegate
// 这个方法是在HTML中调用了JS的alert()方法时,就会回调此API。
// 注意,使用了`WKWebView`后,在JS端调用alert()就不会在HTML
// 中显示弹出窗口。因此,我们需要在此处手动弹出ios系统的alert。
func webView(webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: () -> Void) {
  let alert = UIAlertController(title: "Tip", message: message, preferredStyle: .Alert)
  alert.addAction(UIAlertAction(title: "Ok", style: .Default, handler: { (_) -> Void in
    // We must call back js
    completionHandler()
  }))
  
  self.presentViewController(alert, animated: true, completion: nil)
}
 
func webView(webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: (Bool) -> Void) {
  let alert = UIAlertController(title: "Tip", message: message, preferredStyle: .Alert)
  alert.addAction(UIAlertAction(title: "Ok", style: .Default, handler: { (_) -> Void in
    // 点击完成后,可以做相应处理,最后再回调js端
    completionHandler(true)
  }))
  alert.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: { (_) -> Void in
    // 点击取消后,可以做相应处理,最后再回调js端
    completionHandler(false)
  }))
  
  self.presentViewController(alert, animated: true, completion: nil)
}
 
func webView(webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: (String?) -> Void) {
  let alert = UIAlertController(title: prompt, message: defaultText, preferredStyle: .Alert)
  
  alert.addTextFieldWithConfigurationHandler { (textField: UITextField) -> Void in
    textField.textColor = UIColor.redColor()
  }
  alert.addAction(UIAlertAction(title: "Ok", style: .Default, handler: { (_) -> Void in
     // 处理好之前,将值传到js端
    completionHandler(alert.textFields![0].text!)
  }))
  
  self.presentViewController(alert, animated: true, completion: nil)
}
 
func webViewDidClose(webView: WKWebView) {
  print(__FUNCTION__)
}
 

WKScriptMessageHandler

接下来,我们看看WKScriptMessageHandler,这个是注入js名称,在js端通过window.webkit.messageHandlers.{InjectedName}.postMessage()方法来发送消息到native。我们需要遵守此协议,然后实现其代理方法,就可以收到消息,并做相应处理。这个协议只有一个方法:

 
1
2
3
4
5
6
7
8
9
10
 
// MARK: - WKScriptMessageHandler
func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {
  print(message.body)
  // 如果在开始时就注入有很多的名称,那么我们就需要区分来处理
  if message.name == "AppModel" {
    print("message name is AppModel")
  }
}
 

这个方法是相当好的API,我们给js注入一个名称,就会自动转换成js的对象,然后就可以发送消息到native端。

WKNavigationDelegate

还有一个非常关键的代理WKNavigationDelegate,这个代理有很多的代理方法,可以控制页面导航。

其调用顺序为: 1、这个代理方法是用于处理是否允许跳转导航。对于跨域只有Safari浏览器才允许,其他浏览器是不允许的,因此我们需要额外处理跨域的链接。

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 
// 决定导航的动作,通常用于处理跨域的链接能否导航。WebKit对跨域进行了安全检查限制,不允许跨域,因此我们要对不能跨域的链接
// 单独处理。但是,对于Safari是允许跨域的,不用这么处理。
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
    print(__FUNCTION__)
        
    let hostname = navigationAction.request.URL?.host?.lowercaseString
        
    print(hostname)
    // 处理跨域问题
    if navigationAction.navigationType == .LinkActivated && !hostname!.containsString(".baidu.com") {
      // 手动跳转
      UIApplication.sharedApplication().openURL(navigationAction.request.URL!)
      
      // 不允许导航
      decisionHandler(.Cancel)
    } else {
      self.progressView.alpha = 1.0
      
      decisionHandler(.Allow)
    }
}
 

2、开始加载页面内容时就会回调此代理方法,与UIWebViewdidStartLoad功能相当

 
1
2
3
4
5
 
func webView(webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
    print(__FUNCTION__)
}
 

3、决定是否允许导航响应,如果不允许就不会跳转到该链接的页面。

 
1
2
3
4
5
6
 
func webView(webView: WKWebView, decidePolicyForNavigationResponse navigationResponse: WKNavigationResponse, decisionHandler: (WKNavigationResponsePolicy) -> Void) {
    print(__FUNCTION__)
    decisionHandler(.Allow)
}
 

4、Invoked when content starts arriving for the main frame.这是API的原注释。也就是在页面内容加载到达mainFrame时会回调此API。如果我们要在mainFrame中注入什么JS,也可以在此处添加。

 
1
2
3
4
5
 
func webView(webView: WKWebView, didCommitNavigation navigation: WKNavigation!) {
  print(__FUNCTION__)
}
 

5、加载完成的回调

 
1
2
3
4
5
 
func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) {
  print(__FUNCTION__)
}
 

如果加载失败了,会回调下面的代理方法:

 
1
2
3
4
5
 
func webView(webView: WKWebView, didFailNavigation navigation: WKNavigation!, withError error: NSError) {
  print(__FUNCTION__)
}
 

其实在还有一些API,一般情况下并不需要。如果我们需要处理在重定向时,需要实现下面的代理方法就可以接收到。

 
1
2
3
4
5
 
func webView(webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) {
  print(__FUNCTION__)
}
 

如果我们的请求要求授权、证书等,我们需要处理下面的代理方法,以提供相应的授权处理等:

 
1
2
3
4
5
6
 
func webView(webView: WKWebView, didReceiveAuthenticationChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
    print(__FUNCTION__)
    completionHandler(.PerformDefaultHandling, nil)
}
 

当我们终止页面加载时,我们会可以处理下面的代理方法,如果不需要处理,则不用实现之:

 
1
2
3
4
5
 
func webViewWebContentProcessDidTerminate(webView: WKWebView) {
    print(__FUNCTION__)
}
 

源代码

具体代码已经发布到github:https://github.com/CoderJackyHuang/WKWebViewTestDemo

总结

苹果已经向我们提供了WKWebView,拥有UIWebView的所有功能,且还提供更多的功能,明示是为了替代UIWebView,但是WKWebView要在ios8.0之后才能使用,因此,如果我们使用Swift来开发应用,兼容版本从8.0开始时,可以直接使用WKWebView

我们可以发现,苹果提供了更多简便的方式让native与js交互更加方便,通过让native注入名称,然后在js端自动转换成js的对象,就可以在js端通过对象的方式来发送消息到native端。如此一来,就简化了js与native的交互了。

最新文章

  1. ubuntu下增加中文编码
  2. 在Entity Framework 7中进行数据迁移
  3. 获取QQ企业邮箱通讯录PY脚本
  4. long型转日期型
  5. ZOJ-3593 One Person Game 概率DP
  6. NSCharacterset
  7. Html——footer的使用
  8. logstash ArgumentError: comparison of String with 5 failed
  9. ASP.NET MVC 5 学习教程:添加查询
  10. OA办公系统功能真的越全越好?
  11. 洛谷 P1967 货车运输
  12. 【开源项目】智能电视及电视盒子的控制应用TVRemoteIME的接口说明
  13. springboot动态多数据源
  14. java 并发 concurrent Executor
  15. centos7 安装php7,报错cannot get uid for user nginx
  16. 从零搭建java后台管理系统(二)mysql和redis安装
  17. vuejs实现瀑布流布局(三)
  18. ELK日志收集分析平台 (Elasticsearch+Logstash+Kibana)使用说明
  19. C# IEqualityComparer类型参数写法
  20. 服务端REST与SOAP的探讨(转)

热门文章

  1. [Machine Learning]k-NN
  2. 如何修改DBSNMP和SYSMAN用户的密码
  3. node.js学习(1)
  4. Leetcode: Rotate Function
  5. Leetcode: Count Numbers with Unique Digits
  6. 树形DP +01背包(HDU 1011)
  7. ACM-ICPC竞赛模板
  8. 三台CentOS 5 Linux LVS 的DR 模式http负载均衡安装步骤
  9. C语言 类型
  10. SQL2008无法连接到.\SQLEXPRESS,用户&#39;sa&#39;登录失败(错误18456)图文解决方法