在iOS 8之后,苹果推出了WKWebView来取代过时的UIWebView,在 iOS 13 中宣布将不再支持 UIWebView。WKWebView 是使用 WebKit 框架提供的一种高性能的网页浏览器控件,在性能和安全方面有很多优势。
在WKWebView中,你可以使用WKWebViewConfiguration的WKPreferences属性来替代UIWebView中的各种属性配置方法,具体可查看API文档。比如:
// 创建 WKWebViewConfiguration 对象
let configuration = WKWebViewConfiguration()
//在UIWebView中,dataDetectorTypes属性用于指定自动检测和识别链接、
//电话号码、日期等类型的数据。而在WKWebView中,相应的功能由其配
//置对象(WKWebViewConfiguration)中的dataDetectorTypes属性来实现。
// 获取 dataDetectorTypes 属性
let dataDetectorTypes = configuration.dataDetectorTypes
// 设置需要检测和识别的数据类型
dataDetectorTypes = [.link, .phoneNumber, .calendarEvent]
// 获取 preferences 属性
let preferences = configuration.preferences
// 设置 属性
preferences.javaScriptCanOpenWindowsAutomatically = true
preferences.javaScriptEnabled = true
...
// 使用 configuration 创建 WKWebView
let webView = WKWebView(frame: CGRect.zero, configuration: configuration)
// 获取WKWebView的scrollView属性
if let scrollView = webView.scrollView {
// 设置contentMode属性
scrollView.contentMode = .scaleAspectFit
// 设置minimumZoomScale和maximumZoomScale属性
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 10.0
}
下面是UIWebView和WKWebView的一些常见代理方法对比:
UIWebView的代理方法
webViewDidStartLoad(_:):开始加载网页时调用。
webViewDidFinishLoad(_:):网页加载完成时调用。
didFailLoadWithError(_:):网页加载发生错误时调用。
shouldStartLoadWithRequest(_:navigationType:):决定是否加载特定请求的网页。
WKWebView的代理方法
webView(_:, didStartProvisionalNavigation:):开始加载网页时调用。
webView(_:, didFinish:):网页加载完成时调用。
webView(_:, didFail:, withError:):网页加载发生错误时调用。
webView(_:, decidePolicyFor:, decisionHandler:):决定是否加载特定请求的网页。
值得注意的是,WKWebView的代理方法命名更加清晰,并且提供了更多的功能。例如,decidePolicyFor方法允许您根据需求自定义网页加载策略。
要替换UIWebView的代理方法为WKWebView的代理方法,需要做以下几个步骤:
1、创建WKWebView实例并设置代理:
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
// 初始化WKWebView
webView = WKWebView(frame: view.bounds)
webView.navigationDelegate = self
// 将webView添加到视图中
view.addSubview(webView)
}
// ...
}
2、实现WKNavigationDelegate协议中的方法来处理网页加载过程:
extension ViewController {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
// 在这里根据你的逻辑判断是否允许加载导航行为
let url = navigationAction.request.url
if shouldAllowNavigation(url) {
decisionHandler(.allow)//相当于 UIWebview中的return YES
} else {
decisionHandler(.cancel) //相当于 UIWebview中的return NO
}
//请注意,decisionHandler是一个异步闭包,你需要在适当的时机调用它来告知WKWebView是否允许加载导航行为。
}
// 网页开始加载时调用
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
// 处理开始加载事件
}
// 网页加载完成时调用
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// 处理加载完成事件
}
// 网页加载失败时调用
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
// 处理加载失败事件
}
// 其他WKNavigationDelegate方法...
}
3、如果你还需要处理JavaScript与原生代码的交互,可以实现WKScriptMessageHandler协议中的方法:
extension ViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
// 处理JavaScript与原生代码的交互
}
}
4、UI方面代理 WKUIDelegate
//该方法在网页中弹出 JavaScript Alert 对话框时被调用,你可以通过实现该方法来自定义 Alert 对话框的显示和处理逻辑。
webView(_:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler:):
//该方法在网页中弹出 JavaScript Confirm 对话框时被调用,你可以通过实现该方法来自定义 Confirm 对话框的显示和处理逻辑。
webView(_:runJavaScriptConfirmPanelWithMessage:initiatedByFrame:completionHandler:):
//该方法在网页中弹出 JavaScript Input 对话框时被调用,你可以通过实现该方法来自定义 Input 对话框的显示和处理逻辑。
webView(_:runJavaScriptTextInputPanelWithPrompt:defaultText:initiatedByFrame:completionHandler:):
在UIWebView中 判断是否顶级导航
BOOL isTopLevelNavigation = [webView.request.mainDocumentURL isEqual:request.URL]
使用WKWebView实现等效功能的示例代码:
if let mainDocumentURL = webView.url, let requestURL = navigationAction.request.url {
let isTopLevelNavigation = mainDocumentURL == requestURL
if isTopLevelNavigation {
// 是顶级导航
print("是顶级导航")
} else {
// 不是顶级导航
print("不是顶级导航")
}
} else {
// 无法判断是否为顶级导航
print("无法判断是否为顶级导航")
}
在使用 WKWebView 加载网页后,可以通过 WKWebView 的 title
属性来获取网页的标题、estimatedProgress
获取加载进度,具体操作如下:
-
首先,确保你已经创建并初始化了 WKWebView 对象,例如:
let webView = WKWebView(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
-
在加载网页之后,可以通过添加观察者(KVO)来监测
title
属性的变化,以便获取最新的网页标题。首先,添加观察者:webView.addObserver(self, forKeyPath: "title", options: .new, context: nil) webView.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)
-
然后,在你的代码中实现
observeValue(forKeyPath:keyPath:of:change:context:)
方法,用于处理观察到的属性变化事件,并获取网页的标题:override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if keyPath == "title" { if let title = webView.title { print("网页标题:\(title)") } } else if keyPath == "estimatedProgress" { print("进度:\(webView.estimatedProgress)") } }
-
最后,在你不需要监听标题变化时,别忘记移除观察者:
webView.removeObserver(self, forKeyPath: "title") webView.removeObserver(self, forKeyPath: "estimatedProgress")
evaluateJavaScript 用信号量卡死问题
/*
JavaScript的调用 替换UIwebview 中的stringByEvaluatingJavaScriptFromString
把异步调用修改为同步调用的方法
需要注意:NSRunLoop这里不可以使用dispatch_semaphore_t信号量代替,会导致永久等待的,已经实测过了。
*/
- (id)syncEvalJavascriptString:(NSString *)jsCode {
__block id returnValue = nil;
__block BOOL finished = NO;
[self evaluateJavaScript:jsCode completionHandler:^(id _Nullable result, NSError * _Nullable error) {
returnValue = result;
finished = YES;
}];
while (!finished) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
return returnValue;
}
清缓存
- (void)clearAllUIWebViewData {
// Clear cache...
[[NSURLCache sharedURLCache] removeAllCachedResponses];
[self removeCachesWithout:@"data"];
[self removeLibraryDirectory:@"WebKit"];
// Clear cookie
for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
[[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie];
}
[self removeLibraryDirectory:@"Cookies"];
}
- (void)removeLibraryDirectory:(NSString *)dirName {
NSString *dir = [[[[NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSUserDomainMask, YES) lastObject]stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"Library"] stringByAppendingPathComponent:dirName];
if ([[NSFileManager defaultManager] fileExistsAtPath:dir]) {
[[NSFileManager defaultManager] removeItemAtPath:dir error:nil];
}
}
- (void)removeCachesWithout:(NSString*)dirName {
if (dirName.length < 1) {
// 如果没有过滤的文件或文件夹,则将整个Caches删掉
[self removeLibraryDirectory:@"Caches"];
return;
}
NSString *dir = [[[[NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSUserDomainMask, YES) lastObject]stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"Library"] stringByAppendingPathComponent:@"Caches"];
if ([[NSFileManager defaultManager] fileExistsAtPath:dir]) {
NSDirectoryEnumerator *myDirectoryEnumerator = [[NSFileManager defaultManager] enumeratorAtPath:dir];
NSString *file;
while((file = [myDirectoryEnumerator nextObject])) {
// 遍历当前目录
if([file isEqualToString:dirName]) {
// 如果需要过滤掉data文件夹不要被删除
} else {
[[NSFileManager defaultManager] removeItemAtPath:[dir stringByAppendingPathComponent:file] error:nil];
}
}
}
}