美文网首页
WKWebView 与 UIWebView

WKWebView 与 UIWebView

作者: FlowYourHeart | 来源:发表于2023-09-14 15:15 被阅读0次

    在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获取加载进度,具体操作如下:
    1. 首先,确保你已经创建并初始化了 WKWebView 对象,例如:

      let webView = WKWebView(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
      
    2. 在加载网页之后,可以通过添加观察者(KVO)来监测 title 属性的变化,以便获取最新的网页标题。首先,添加观察者:

      webView.addObserver(self, forKeyPath: "title", options: .new, context: nil)
      webView.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)
      
    3. 然后,在你的代码中实现 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)")
          }
      }
      
    4. 最后,在你不需要监听标题变化时,别忘记移除观察者:

      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];
                }
            }
        }
    }
    

    相关文章

      网友评论

          本文标题:WKWebView 与 UIWebView

          本文链接:https://www.haomeiwen.com/subject/udcpvdtx.html