语言: swift, 版本:4.2,XCode:10.2
写作时间:2019-04-23
WKWebView在iOS 8 以后提供的高性能的Web容器. 本文列举15个最常用的用法。
在viewDidLoad() 中创建WKWebView,并增加为view的subView或者直接替换掉view. 这样做的弊端是,后续想要用到WKWebView就比较困难.
一种简单的方式是增加一个WKWebView的属性:
let webView = WKWebView()
在 loadView() 的方法去设置ViewController‘s view:
override func loadView() {
self.view = webView
}
拥有一个WKWebView的属性,可以方便后面引用webview的属性和方法。
WKWebView最重要的功能是加载一个远程内容的url, 它比较古怪不是一句代码调用. 反而,先用string创建一个URL对象, 接着封装为URLRequest对象, 最后通知WKWebView去加载URLRequest对象:
func loadRemoteUrl(_ urlString: String) {
if let url = URL(string: urlString) {
let request = URLRequest(url: url)
webView.load(request)
}
}
如果比较频繁调用远程URLs,可以给WKWebView封装一个extension,使加载URL变成一句话方法:
extension WKWebView {
func load(_ urlString: String) {
if let url = URL(string: urlString) {
let request = URLRequest(url: url)
load(request)
}
}
}
现在可以一句话加载远程内容URL
webView.load("https://www.apple.com").
WKWebView可以用 loadFileURL() 加载已经预置在bundle里面的HTML. 你可以提供File URL指向bundle中HTML文件.
举个?, 如果你要加载一个文件叫 “help.html” :
func loadLocalFile() {
if let url = Bundle.main.url(forResource: "help", withExtension: "html") {
let path = url.deletingLastPathComponent()
print("url: \(url)")
print("path: \(path)")
webView.loadFileURL(url, allowingReadAccessTo: path)
}
}
help.html , 这里help.css的路径为相对路径,因为Bundle下面的路径都会被打包到根目录下,详情请看下面的文件截图。
<html>
<head>
<link href="help.css" rel="stylesheet" />
head>
<body>
<h1>Hi, do you need help?h1>
<a href="https://www.apple.com">apple.coma>
<div id="username">@zgpeacediv>
phone:<span class="nowrap">18686868866span>
body>
html>
url.deletingLastPathComponent()告诉WebKit它可以读取在目录下,help.html引用的文件,比如images, JavaScript, CSS.
help.css
h1 {
margin-top:200px;
color:red;
}
参数allowingReadAccessTo 用url, 或者url.deletingLastPathComponent(),都能加载出css的样式。难道是目录如果是同一个文件夹都可以用这两种方式来用?知情者帮忙指正,谢谢。
WKWebView可以直接加载HTML代码字符串:
func loadHtmlFragments() {
let html = """
<html>
<head>
<link href="help.css" rel="stylesheet" />
</head>
<body>
<h1>Hello, Swift!</h1>
</body>
</html>
"""
// webView.loadHTMLString(html, baseURL: nil)
webView.loadHTMLString(html, baseURL: Bundle.main.resourceURL)
}
方法webView.loadHTMLString(html, baseURL: nil)中注意参数baseURL. 如果你引用相关资源比如 images, 或者 CSS, 你可以指定 Bundle.main.resourceURL:
webView.loadHTMLString(html, baseURL: Bundle.main.resourceURL)
WKWebView默认允许访问所有网站, 当然这个权限是可以配置的.
首先,需要把类遵循代理协议 WKNavigationDelegate – 它可以是view controller, 也可以是其它类. 比如:
class ViewController: UIViewController, WKNavigationDelegate {
接着, 设置webView的navigation delegate为当前类:
webView.navigationDelegate = self
最后, 实现方法decidePolicyFor, 增加逻辑决定哪些页面可以加载。作为例子, 下面的实现允许访问host为www.apple.com的网站:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if let host = navigationAction.request.url?.host {
if host == "www.apple.com" {
decisionHandler(.allow)
return
}
}
decisionHandler(.cancel)
}
通常,有些链接在App内部打开,有些链接通过外部浏览器打开。实现这个功能不负责,归功于WKNavigationDelegate protocol.
首先, 标志类遵循代理协议WKNavigationDelegate:
class ViewController: UIViewController, WKNavigationDelegate {
接着, 设置webView的navigation delegate为当前类:
webView.navigationDelegate = self
最后, 实现代理方法decidePolicyFor,表明哪些逻辑用内部App还是外部浏览器打开. 外部浏览器打开用方法UIApplication.shared.open() ,接着需要断开App内部打开的逻辑,用方法decisionHandler(),参数用.cancel .
举个?, 这里的实现host为www.apple.com的网站用外部浏览器打开:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url {
if url.host == "www.apple.com" {
UIApplication.shared.open(url)
decisionHandler(.cancel)
return
}
}
decisionHandler(.allow)
}
加载web页面流程包括,抓取相关HTML, 下载用到JavaScript,CSS, images, 等.
为了提升用户体验,需要告诉用户加载的进度. 实现这需要观察observing属性estimatedProgress :
webView.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: .new, context: nil)
增加观察者属性后,接着实现通知方法observeValue(forKeyPath:) , 它可以通知你一个字符串改变的内容. 如果是属性“estimatedProgress”的改变,可以通过进度条的方式告诉用户,这里只是 打印进度信息:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "estimatedProgress" {
print(Float(webView.estimatedProgress))
}
}
你可以用属性webView.title去读取当前页面的标题, 因为页面的标题会跟着用户浏览不同的页面而改变,实时修改页面的标题可以提升用户体验.
实现这个,首先注册观察的属性:
webView.addObserver(self, forKeyPath: #keyPath(WKWebView.title), options: .new, context: nil)
最后实现通知的方法observeValue(forKeyPath:) method. 这里会传递给你更新的标题,可以更新导航栏信息.
这里只是打印更新的标题:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "title" {
if let title = webView.title {
print(title)
}
}
}
浏览器可以前进和后退,就是因为这些痕迹都被记录. 这些都存储在webView的属性backForwardList中, 它包含了两个数组backList, forwardList.
在这两个数组中,你可以访问所有前进和后退的页面,一般会用到标题和链接url:
func printHistoryBackList() {
for page in webView.backForwardList.backList {
print("User visited title:\(page.title), url: \(page.url.absoluteString)")
}
}
一旦webView加载内容,你可以调用方法evaluateJavaScript()去执行任何JavaScript到已渲染完毕的页面. 你仅仅需要执行JavaScript – 读取网页上的内容, 比如 – 当JavaScript运行后,下面的闭包执行.
举个?, 你的页面包含内容
and you wanted to read the “@zgpeace” , 你可以通过下面代码获取:
func injectJavaScriptIntoAPage() {
webView.evaluateJavaScript("document.getElementById('username').innerText") { (result, error) in
if let result = result {
print(result)
}
}
}
可以通过webView属性httpCookieStore读取cookies列表. 这是藏在configuration.websiteDataStore的属性, 一旦你找到它可以调用方法getAllCookies()获取cookies数组, 或者调用方法delete()去删除指定的cookie.
举个?, 下面代码遍历所有cookies, 如果找到名字叫“authentication”则删掉它 – 其它的cookies则打印出来:
func manageCookies() {
webView.configuration.websiteDataStore.httpCookieStore.getAllCookies { (cookies) in
for cookie in cookies {
if cookie.name == "authentication" {
self.webView.configuration.websiteDataStore.httpCookieStore.delete(cookie, completionHandler: nil)
} else {
print("\(cookie.name) is set to \(cookie.value)")
}
}
}
}
User agents可以告诉服务器端web servers,去鉴别客户端浏览器就是App里面的,常用于访问一些受限制的资源.
如果你从服务端读取页面,你可以设置user agent,使服务器端可以鉴别是你的App:
func customUserAgent() {
print("old agent: \(String(describing: webView.customUserAgent))")
webView.customUserAgent = "My Awesome App"
print("new agent: \(String(describing: webView.customUserAgent))")
}
注意: 你可以修改user agent,当你去访问其它资源的时候。但是记住,有些网站可能会困惑,浏览器怎么会是没见过的名字.
WKWebView就像只有一个长条tab的iOS Safari app, 说明用户不能打开或者关闭一个新的windows去浏览多个页面, 并且它不能由JavaScript触发后显示Alert或者Confirmation确认请求弹框.
幸运的是,你可以实现上面的诉求通过代理协议WKUIDelegate: 设置webView的UI delegate, 你就可以显示定制的alerts, 管理你的tabs, 等.
首先,设置类遵循协议WKUIDelegate:
class ViewController: UIViewController, WKUIDelegate {
接着,指派webView的uiDelegate属性为当前对象:
webView.uiDelegate = self
最后,实现WKUIDelegate的方法.
举个?,通过WKWebView显示alert controller, 当html页面用JavaScript的方法alert():
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
let ac = UIAlertController(title: "Hey, listen!", message: message, preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
present(ac, animated: true)
completionHandler()
}
还有其它代理方法,比如runJavaScriptConfirmPanelWithMessage显示确定和取消按钮的UI, runJavaScriptTextInputPanelWithPrompt 显示text input控件,索取用户反馈, 等等.
注意: 在执行结束后,你必须调用completion handler. JavaScript’s alerts会被阻塞, 这就意味着JavaScript execution不会显示Alert页面直到completion handler 结束. 结果是, WebKit会投诉如果你不告诉它已经处理完毕,告诉的方法就是completionHandler().
测试代码
func showingAlertUI() {
webView.evaluateJavaScript("alert('boom');", completionHandler: nil)
}
虽然你可以用方法drawHierarchy(),把view转换为image, WebKit可以用方法takeSnapshot()剪切任意大小的image.
举个?,可以从左上角开始剪切150x50 image, 并把剪切的页面当做subView显示于view上面:
func snapshotPartOfThePage() {
let config = WKSnapshotConfiguration()
config.rect = CGRect(x: 0, y: 200, width: 150, height: 50)
webView.takeSnapshot(with: config) { (image, error) in
if let image = image {
print(image.size)
let imageView = UIImageView(image: image)
self.view.addSubview(imageView)
}
}
}
如果你要截屏整个view – 把cofnig改为nil即可.
WebViews已经支持数据类型检测, 比如电话号码 phone numbers, 日历事件calendar events, 航班号码flight numbers都可以编程可点击的链接 tappable links.
上面数据类型默认是普通文本, 但是可以被覆盖 – 当你创建webView时候 ,定制参数对象WKWebViewConfiguration.
举个?, 这里命令webView去检测所有可能地数据类型:
let config = WKWebViewConfiguration()
config.dataDetectorTypes = [.all]
let webView = WKWebView(frame: .zero, configuration: config)
_blank 问题:
a便签中如果有_blank属性,表示在新的窗口打开页面,但是我们打不开,解决办法是添加如下代理的实现,直接在当前窗口加载url
-(WKWebView )webView:(WKWebView )webView createWebViewWithConfiguration:(WKWebViewConfiguration )configuration forNavigationAction:(WKNavigationAction )navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{
if (!navigationAction.targetFrame.isMainFrame) {
[webView loadRequest:navigationAction.request];
}
return nil;
}
cookie问题、白屏问题
参考 WKWebView 那些坑解决的,都有不错的解决方法
WKWebView有两个代理,WKUIDelegate 和 WKNavigationDelegate。WKNavigationDelegate主要处理一些跳转、加载处理操作;WKUIDelegate主要处理UI相关的操作:确认框,警告框,提示框。因此WKNavigationDelegate更加常用。下面是WKNavigationDelegate的代理列表:
///二、页面是否加载 ,相当于shouldStartLoadWithRequest
- (void) webView: (WKWebView *) webView decidePolicyForNavigationAction: (WKNavigationAction*) navigationAction decisionHandler: (void (^)(WKNavigationActionPolicy)) decisionHandler
{
//允许跳转
decisionHandler(WKNavigationResponsePolicyAllow);
//不允许跳转
//decisionHandler(WKNavigationResponsePolicyCancel);
}
///三、页面开始加载
- (void)webView:(WKWebView*)webView didStartProvisionalNavigation:(WKNavigation*)navigation
{
}
///四、收到响应
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
{
//允许跳转
decisionHandler(WKNavigationResponsePolicyAllow);
//不允许跳转
//decisionHandler(WKNavigationResponsePolicyCancel);
}
///五、收到数据
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation
{
}
///六、页面加载完成
- (void)webView:(WKWebView*)webView didFinishNavigation:(WKNavigation*)navigation
{
}
///七、页面加载失败
- (void)webView:(WKWebView*)theWebView didFailNavigation:(WKNavigation*)navigation withError:(NSError*)error
{
}
/// 八、跳转失败
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error
{
}
/// 九、重定向
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation
{
}
///十、这个目的解决白屏问题 (ios9以上)
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView API_AVAILABLE(macosx(10.11), ios(9.0))
{
[self loadRequestWithUrl:self.strCurrentURL];
}
WKUIDelegate有下面三个代理:
- (void) webView:(WKWebView*)webView
runJavaScriptAlertPanelWithMessage:(NSString*)message
initiatedByFrame:(WKFrameInfo*)frame
completionHandler:(void (^)(void))completionHandler
- (void) webView:(WKWebView*)webView
runJavaScriptConfirmPanelWithMessage:(NSString*)message
initiatedByFrame:(WKFrameInfo*)frame
completionHandler:(void (^)(BOOL result))completionHandler
- (void) webView:(WKWebView*)webView
runJavaScriptTextInputPanelWithPrompt:(NSString*)prompt
defaultText:(NSString*)defaultText
initiatedByFrame:(WKFrameInfo*)frame
completionHandler:(void (^)(NSString* result))completionHandler
https://github.com/zgpeace/WKWebViewGuide
https://www.hackingwithswift.com/articles/112/the-ultimate-guide-to-wkwebview
http://www.cnblogs.com/NSong/p/6489802.html
https://jianli2017.top/wiki/Hybird/webview/gome-Webview/