【问题标题】:How to capture a full page screenshot of WKWebview?如何截取 WKWebview 的整页截图?
【发布时间】:2016-05-26 05:19:45
【问题描述】:

我可以通过调整UIWebViewUIScrollView的框架来捕获UIWebView的所有内容截图。但是我不能用同样的方法截取WKWebView的所有内容截图。

我用来捕获UIWebView的方法如下:

  1. webview.scrollView的备份框架和superview
  2. 创建一个新的容器视图作为webview.scrollView 的超级视图
  3. 调整新容器视图的框架和webview.scrollView。框架同webview.scrollView.contentSize
  4. renderInContextdrawViewHierarchyInRect 绘制

但是,此方法将捕获WKWebview 的白色屏幕截图。没用!

我打印了WKWebView的所有级别,然后我发现UIView(WKContentView)的大小与contentView相同,你可以通过这个级别找到这个视图:

  • WKWeb 视图
    • WKScrollView
      • WKContentView(大小与 contentView 相同)

我也曾尝试通过WKContentView 捕捉,然后我发现只能捕捉可见视图。

无论如何,谁能告诉我如何截取WKWebView的整页内容截图?

【问题讨论】:

  • 我已经通过创建许多屏幕截图来捕获 WKWebView 并将其组合成一个大的 UIImage。但是,在这种情况下,display: absolute; div 将被绘制多次。
  • 我写了一个lib来捕获WKWebView:github.com/startry/SwViewCapture,但是还是有display: absolute div重复的问题。
  • display: absolute; -> position: absolute;

标签: ios wkwebview


【解决方案1】:

这里的关键是让 webkit 之后的捕获在给定新帧后有时间渲染。我尝试了 didFinishLoading 回调和 WKWebView 的wkWebView.takeSnapshot,但没有奏效。 所以我为截图引入了延迟:

    func createImage(webView: WKWebView, completion: @escaping (UIImage?) -> ()) {
    
    // save the original size to restore
    let originalFrame = webView.frame
    let originalConstraints = webView.constraints
    let originalScrollViewOffset = webView.scrollView.contentOffset
    
    let newSize = webView.scrollView.contentSize
    
    // remove any constraints for the web view, and set the size
    // to be size of the content size (will be restored later)
    webView.removeConstraints(originalConstraints)
    webView.translatesAutoresizingMaskIntoConstraints = true
    webView.frame = CGRect(origin: .zero, size: newSize)
    webView.scrollView.contentOffset = .zero
    
    // wait for a while for the webview to render in the newly set frame
    DispatchQueue.main.asyncAfter(deadline: .now() + 4.0) {
        defer {
            UIGraphicsEndImageContext()
        }
        UIGraphicsBeginImageContextWithOptions(newSize, false, 0)
        if let context = UIGraphicsGetCurrentContext() {
            // render the scroll view's layer
            webView.scrollView.layer.render(in: context)
            
            // restore the original state
            webView.frame = originalFrame
            webView.translatesAutoresizingMaskIntoConstraints = false
            webView.addConstraints(originalConstraints)
            webView.scrollView.contentOffset = originalScrollViewOffset
            
            if let image = UIGraphicsGetImageFromCurrentImageContext() {
                completion(image)
            } else {
                completion(nil)
            }
        }
    }
}

结果:

【讨论】:

  • defer 应该放在异步块中
  • @guoc 正确。已编辑。
【解决方案2】:

这是一个 iOS 11+ 示例,用于对 WKWebView 进行快照无需任何延迟即可呈现内容。

关键是确保您不会收到空白快照。最初,我们遇到了输出图像为空白的问题,并且必须引入延迟才能在输出图像中包含 Web 内容。我在许多帖子中看到了延迟的解决方案,我建议不要使用它,因为它是一个不必要的不​​可靠和性能不佳的解决方案。对我们来说重要的解决方案是设置 WKSnapshotConfigurationrect 并使用 JavaScript 函数等待渲染并接收正确的宽度和高度。

设置 WKWebView:

// Important: You can set a width here which will also be reflected in the width of the snapshot later
let webView = WKWebView(frame: .zero)

webView.configuration.dataDetectorTypes = []
webView.navigationDelegate = self

webView.scrollView.contentInsetAdjustmentBehavior = .never

webView.loadHTMLString(htmlString, baseURL: Bundle.main.resourceURL)

捕获快照:

func webView(
    _ webView: WKWebView,
    didFinish navigation: WKNavigation!
) {
    // Wait for the page to be rendered completely and get the final size from javascript
    webView.evaluateJavaScript("document.readyState", completionHandler: { [weak self] (readyState, readyStateError) in
        webView.evaluateJavaScript("document.documentElement.scrollWidth", completionHandler: {(contentWidth, widthError) in
            webView.evaluateJavaScript("document.documentElement.scrollHeight", completionHandler: { (contentHeight, heightError) in
                guard readyState != nil,
                    let contentHeight = contentHeight as? CGFloat,
                    let contentWidth = contentWidth as? CGFloat else {
                    [Potential error handling...]
                    return
                }

                let rect = CGRect(
                    x: 0,
                    y: 0,
                    width: contentWidth,
                    height: contentHeight
                )

                let configuration = WKSnapshotConfiguration()

                configuration.rect = rect

                if #available(iOS 13.0, *) {
                    configuration.afterScreenUpdates =  true
                }

                webView.takeSnapshot(with: configuration) { (snapshotImage, error) in
                    guard let snapshotImage = snapshotImage else {
                        [Potential error handling...]
                    }

                    [Do something with the image]
                }
            })
        })
    })
}

【讨论】:

    【解决方案3】:

    - xcode 10 和 swift 4.2 -

    使用这个;

    //这是截图的按钮

       @IBAction func snapShot(_ sender: Any) {
    
           captureScreenshot()
        }
    

    // 这是句柄截图功能的函数。

    func captureScreenshot(){
        let layer = UIApplication.shared.keyWindow!.layer
        let scale = UIScreen.main.scale
        // Creates UIImage of same size as view
        UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, scale);
        layer.render(in: UIGraphicsGetCurrentContext()!)
        let screenshot = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        // THIS IS TO SAVE SCREENSHOT TO PHOTOS
        UIImageWriteToSavedPhotosAlbum(screenshot!, nil, nil, nil)
    }
    

    并且您必须在 info.plist 中添加这些键;

     key value = "Privacy - Photo Library Additions Usage Description" ,
     key value = Privacy - Photo Library Usage Description
    

    【讨论】:

      【解决方案4】:

      请参考这个answer

      iOS 11.0 及以上版本,Apple 提供了以下 API 来捕获 WKWebView 的快照。

      @available(iOS 11.0, *)
          open func takeSnapshot(with snapshotConfiguration: WKSnapshotConfiguration?, completionHandler: @escaping (UIImage?, Error?) -> Swift.Void)
      

      【讨论】:

        【解决方案5】:

        Swift 3,Xcode 9: 我创建了一个扩展来实现这一点。希望对您有所帮助。

        extension WKWebView{
        
             private func stageWebViewForScreenshot() {
                let _scrollView = self.scrollView
                let pageSize = _scrollView.contentSize;
                let currentOffset = _scrollView.contentOffset
                let horizontalLimit = CGFloat(ceil(pageSize.width/_scrollView.frame.size.width))
                let verticalLimit = CGFloat(ceil(pageSize.height/_scrollView.frame.size.height))
        
                 for i in stride(from: 0, to: verticalLimit, by: 1.0) {
                    for j in stride(from: 0, to: horizontalLimit, by: 1.0) {
                        _scrollView.scrollRectToVisible(CGRect(x: _scrollView.frame.size.width * j, y: _scrollView.frame.size.height * i, width: _scrollView.frame.size.width, height: _scrollView.frame.size.height), animated: true)
                         RunLoop.main.run(until: Date.init(timeIntervalSinceNow: 1.0))
                    }
                }
                _scrollView.setContentOffset(currentOffset, animated: false)
            }
        
             func fullLengthScreenshot(_ completionBlock: ((UIImage) -> Void)?) {
                // First stage the web view so that all resources are downloaded.
                 stageWebViewForScreenshot()
        
                let _scrollView = self.scrollView
        
                // Save the current bounds
                let tmp = self.bounds
                let tmpFrame = self.frame
                 let currentOffset = _scrollView.contentOffset
        
                // Leave main thread alone for some time to let WKWebview render its contents / run its JS to load stuffs.
                 mainDispatchAfter(2.0) {
                    // Re evaluate the size of the webview
                    let pageSize = _scrollView.contentSize
                     UIGraphicsBeginImageContext(pageSize)
        
                    self.bounds = CGRect(x: self.bounds.origin.x, y: self.bounds.origin.y, width: pageSize.width, height: pageSize.height)
                    self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: pageSize.width, height: pageSize.height)
        
                    // Wait few seconds until the resources are loaded
                    RunLoop.main.run(until: Date.init(timeIntervalSinceNow: 0.5))
        
                     self.layer.render(in: UIGraphicsGetCurrentContext()!)
                    let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
                    UIGraphicsEndImageContext()
        
                    // reset Frame of view to origin
                    self.bounds = tmp
                    self.frame = tmpFrame
                    _scrollView.setContentOffset(currentOffset, animated: false)
        
                     completionBlock?(image)
                }
            }
        }
        

        【讨论】:

          【解决方案6】:

          斯威夫特 3,Xcode 8

          func takeScreenshot() -> UIImage? { 
              let currentSize = webView.frame.size
              let currentOffset = webView.scrollView.contentOffset
          
              webView.frame.size = webView.scrollView.contentSize
              webView.scrollView.setContentOffset(CGPoint.zero, animated: false)
          
              let rect = CGRect(x: 0, y: 0, width: webView.bounds.size.width, height: webView.bounds.size.height)
              UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.main.scale)
              webView.drawHierarchy(in: rect, afterScreenUpdates: true)
              let image = UIGraphicsGetImageFromCurrentImageContext()
              UIGraphicsEndImageContext()
          
              webView.frame.size = currentSize
              webView.scrollView.setContentOffset(currentOffset, animated: false)
          
              return image
          }
          

          【讨论】:

          • 它没有拍摄完整的 wkwebview 屏幕截图
          • @Yatendra,这是要走的路,但你必须分两次进行,第一次通过使用滚动视图内容大小检测实际大小,将 WebView 框架更新为该大小和刷新或重新加载或您用于加载 webview 的任何内容,现在它将呈现,因为如果大小超过一定大小,它将被裁剪,保存 webview 的调试视图,并在图像编辑器上打开它,你会看到裁剪始终是固定大小,我的情况是高度为 1024 点,低于该高度的所有内容都被裁剪/纯色背景。
          【解决方案7】:

          更新:不知道为什么 Jason 在整个网络上都坚持这个答案。它不能解决如何截取完整的 WKWebView 内容的问题......包括屏幕外的内容。

          试试这个:

          func snapshot() -> UIImage {
                  UIGraphicsBeginImageContextWithOptions(self.webView.bounds.size, true, 0);
                  self.webView.drawViewHierarchyInRect(self.webView.bounds, afterScreenUpdates: true);
                  let snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
                  UIGraphicsEndImageContext();
          
                  UIImageWriteToSavedPhotosAlbum(snapshotImage, nil, nil, nil)
          
                  return snapshotImage
              }
          

          图像将自动保存到 iOS 相机胶卷中(通过 UIImageWriteToSavedPhotosAlbum())。

          【讨论】:

          • 顺便说一句,这是 wkwebview 的方法,我 100% 确定它有效!
          • 但是,它无法捕获WKWebView的所有内容。
          • 感谢您的回答,我自己找到了解决方案。我通过更改 WKWebView 的框架多次捕获视图,然后组合成一个大 UIImage。使用更改的框架而不是滚动 WKWebView 来捕获页面视图。参考:SwViewCapture源码。
          • 太棒了。很高兴你找到你的解决方案:)
          • @Startry 这是正确答案,谢谢!
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-08-31
          • 1970-01-01
          • 1970-01-01
          • 2011-10-25
          • 1970-01-01
          • 1970-01-01
          • 2018-06-19
          相关资源
          最近更新 更多