iOS使用WebView生成长截图的第3种解决方案

iOS使用WebView生成长截图的第3种解决方案,第1张

概述iOS使用WebView生成截图的第3种解决方案 前言 WebView就是一个内嵌浏览器控件,在iOS中主要有两种WebView:UIWebView和WKWebView,UIWebView是iOS2之后开始使用,WKWebView是在iOS8开始使用,WKWebView将逐步取代笨重的UIWebView. 由于项目需要,新近实现了一个长截图库 SnapshotKit.其中,需要支持 UIWebView.WKWebView 组件生成长截图.为了实现这个特性,查阅了很多资料,同时也做了不同的新奇思路尝试,最终实现了一个新的.取巧的技术方案. 以下主

前言

WebVIEw就是一个内嵌浏览器控件,在iOS中主要有两种WebVIEw:UIWebVIEw和WKWebVIEw,UIWebVIEw是iOS2之后开始使用,WKWebVIEw是在iOS8开始使用,WKWebVIEw将逐步取代笨重的UIWebVIEw。

由于项目需要,新近实现了一个长截图库 SnapshotKit。其中,需要支持 UIWebVIEw、WKWebVIEw 组件生成长截图。为了实现这个特性,查阅了很多资料,同时也做了不同的新奇思路尝试,最终实现了一个新的、取巧的技术方案。

以下主要总结了在“WebVIEw生成长截图”需求方面,“网上已有方案”和“我的全新方案”的各自实现要点和优缺点。

WebVIEw生成长截图的已有方案

根据 Google 所搜索到的资料,目前iOS WebVIEw生成长截图的方案主要有2种:

方案一:修改Frame,截图组件方案二:分页截图组件内容,合成长图

下面将会简述方案一和方案二的具体实现。

方案一:修改Frame,截图组件

方案一的实现要点在于:修改 webVIEw.scrollVIEw 的 frameSize  为 contentSize,然后对整个 webVIEw.scrollVIEw 进行截图。

不过,这个方案只适用 UIWebVIEw 组件,因为其是一次性加载网页所有的内容。而 WKWebVIEw 组件,为了节省内存,加载网页内容时,只加载可视部分——这一点类似 UItableVIEw 组件。在修改webVIEw.scrollVIEw 的 frameSize 后,立即执行了截图 *** 作, 这时候,WKWebVIEw由于还没把网页的内容加载出来,导致生成的长截图是空白的。

方案一核心代码如下:

extension UIScrollVIEw { public func takeSnapshotOfFullContent() -> UIImage? {  let originalFrame = self.frame  let originalOffset = self.contentOffset  self.frame = CGRect.init(origin: originalFrame.origin,size: self.contentSize)  self.contentOffset = .zero  let backgroundcolor = self.backgroundcolor ?? UIcolor.white  UIGraphicsBeginImageContextWithOptions(self.bounds.size,true,0)  guard let context = UIGraphicsGetCurrentContext() else {   return nil  }  context.setFillcolor(backgroundcolor.cgcolor)  context.setstrokecolor(backgroundcolor.cgcolor)  self.drawHIErarchy(in: self.bounds,afterScreenUpdates: true)  let image = UIGraphicsGetimageFromCurrentimageContext()  UIGraphicsEndImageContext()  self.frame = originalFrame  self.contentOffset = originalOffset  return image }}

测试代码:

// example code private func takeSnapshotOfUIWebVIEw() { let image = self.webVIEw.scrollVIEw.takeSnapshotOfFullContent() // 处理image} 

方案二:分页截图组件内容,合成长图

方案二的实现要点在于:分页滚动WebVIEw组件的内容,然后生成分页截图,最后把所有分页截图合成一张长图。

这个方案适用于 UIWebVIEw 组件和 WKWebVIEw 组件。

方案二核心代码如下:

extension UIScrollVIEw { public func takeScreenshotOfFullContent(_ completion: @escaPing ((UIImage?) -> VoID)) {  // 分页绘制内容到ImageContext  let originalOffset = self.contentOffset  // 当contentSize.height<bounds.height时,保证至少有1页的内容绘制  var pageNum = 1  if self.contentSize.height > self.bounds.height {   pageNum = Int(floorf(float(self.contentSize.height / self.bounds.height)))  }  let backgroundcolor = self.backgroundcolor ?? UIcolor.white  UIGraphicsBeginImageContextWithOptions(self.contentSize,0)  guard let context = UIGraphicsGetCurrentContext() else {   completion(nil)   return  }  context.setFillcolor(backgroundcolor.cgcolor)  context.setstrokecolor(backgroundcolor.cgcolor)  self.drawScreenshotOfPageContent(0,maxIndex: pageNum) {   let image = UIGraphicsGetimageFromCurrentimageContext()   UIGraphicsEndImageContext()   self.contentOffset = originalOffset   completion(image)  } } fileprivate func drawScreenshotOfPageContent(_ index: Int,maxIndex: Int,completion: @escaPing () -> VoID) {  self.setContentOffset(CGPoint(x: 0,y: CGfloat(index) * self.frame.size.height),animated: false)  let pageFrame = CGRect(x: 0,y: CGfloat(index) * self.frame.size.height,wIDth: self.bounds.size.wIDth,height: self.bounds.size.height)  dispatchQueue.main.asyncAfter(deadline: dispatchTime.Now() + 0.3) {   self.drawHIErarchy(in: pageFrame,afterScreenUpdates: true)   if index < maxIndex {    self.drawScreenshotOfPageContent(index + 1,maxIndex: maxIndex,completion: completion)   }else{    completion()   }  } }}

测试代码:

// example codeprivate func takeSnapshotOfUIWebVIEw() { self.uiWebVIEw.scrollVIEw.takeScreenshotOfFullContent { (image) in  // 处理image }}private func takeSnapshotOfWKWebVIEw() { self.wkWebVIEw.scrollVIEw.takeScreenshotOfFullContent { (image) in  // 处理image }}

WebVIEw生成长截图的新方案

除了方案一和方案二,还有新方案吗?

答案是肯定加确定以及一定的。

这个新方案的要点在于:iOS系统的WebVIEw打印功能。

iOS系统支持把WebVIEw的内容打印到pdf文件上,借助这个特性,新方案的设计如下:

把 WebVIEw组件的内容全部打印到一页pdf上把pdf转换成图片

新方案的核心代码如下:

import UIKitimport WebKit/// WebVIEwPrintPageRenderer: use to print the full content of webvIEw into one imageinternal final class WebVIEwPrintPageRenderer: UIPrintPageRenderer { private var formatter: UIPrintFormatter private var contentSize: CGSize /// 生成PrintPageRenderer实例 /// /// - Parameters: /// - formatter: WebVIEw的vIEwPrintFormatter /// - contentSize: WebVIEw的ContentSize required init(formatter: UIPrintFormatter,contentSize: CGSize) {  self.formatter = formatter  self.contentSize = contentSize  super.init()  self.addPrintFormatter(formatter,startingAtPageAt: 0) } overrIDe var paperRect: CGRect {  return CGRect.init(origin: .zero,size: contentSize) } overrIDe var printableRect: CGRect {  return CGRect.init(origin: .zero,size: contentSize) } private func printContentTopdfpage() -> CGpdfpage? {  let data = NSMutableData()  UIGraphicsBeginPdfcontextToData(data,self.paperRect,nil)  self.prepare(forDrawingPages: NSMakeRange(0,1))  let bounds = UIGraphicsGetPdfcontextBounds()  UIGraphicsBeginpdfpage()  self.drawPage(at: 0,in: bounds)  UIGraphicsEndPdfcontext()  let cfData = data as CFData  guard let provIDer = CGDataProvIDer.init(data: cfData) else {   return nil  }  let pdfdocument = CGpdfdocument.init(provIDer)  let pdfpage = pdfdocument?.page(at: 1)  return pdfpage } private func covertpdfpageToImage(_ pdfpage: CGpdfpage) -> UIImage? {  let pageRect = pdfpage.getBoxRect(.trimBox)  let contentSize = CGSize.init(wIDth: floor(pageRect.size.wIDth),height: floor(pageRect.size.height))  // usually you want UIGraphicsBeginImageContextWithOptions last parameter to be 0.0 as this will us the device's scale  UIGraphicsBeginImageContextWithOptions(contentSize,2.0)  guard let context = UIGraphicsGetCurrentContext() else {   return nil  }  context.setFillcolor(UIcolor.white.cgcolor)  context.setstrokecolor(UIcolor.white.cgcolor)  context.fill(pageRect)  context.saveGState()  context.translateBy(x: 0,y: contentSize.height)  context.scaleBy(x: 1.0,y: -1.0)  context.interpolationQuality = .low  context.setRenderingIntent(.defaultIntent)  context.drawpdfpage(pdfpage)  context.restoreGState()  let image = UIGraphicsGetimageFromCurrentimageContext()  UIGraphicsEndImageContext()  return image } /// print the full content of webvIEw into one image /// /// - important: if the size of content is very large,then the size of image will be also very large /// - Returns: UIImage? internal func printContentToImage() -> UIImage? {  guard let pdfpage = self.printContentTopdfpage() else {   return nil  }  let image = self.covertpdfpageToImage(pdfpage)  return image }}extension UIWebVIEw { public func takeScreenshotOfFullContent(_ completion: @escaPing ((UIImage?) -> VoID)) {  self.scrollVIEw.setContentOffset(CGPoint(x: 0,y: 0),animated: false)  dispatchQueue.main.asyncAfter(deadline: dispatchTime.Now() + 0.3) {   let renderer = WebVIEwPrintPageRenderer.init(formatter: self.vIEwPrintFormatter(),contentSize: self.scrollVIEw.contentSize)   let image = renderer.printContentToImage()   completion(image)  } }}extension WKWebVIEw { public func takeScreenshotOfFullContent(_ completion: @escaPing ((UIImage?) -> VoID)) {  self.scrollVIEw.setContentOffset(CGPoint(x: 0,contentSize: self.scrollVIEw.contentSize)   let image = renderer.printContentToImage()   completion(image)  } }}

WebVIEwPrintPageRenderer 是该方案的核心类,负责把 WebVIEw组件内容打印到pdf,然后把pdf转换为图片。
UIWebVIEw 和 WKWebVIEw 则实现对应的扩展。

测试代码:

// example codeprivate func takeSnapshotOfUIWebVIEw() { self.uiWebVIEw.scrollVIEw.takeScreenshotOfFullContent { (image) in  // 处理image }}private func takeSnapshotOfWKWebVIEw() { self.wkWebVIEw.scrollVIEw.takeScreenshotOfFullContent { (image) in  // 处理image }}

三种技术方案优劣对比

那么,这三种技术方案各自存在什么优缺点呢,适用什么场景呢?

方案一:只适用 UIWebVIEw;若网页内容很多,生成长截图时,会占用过多内存。 所以,该方案只适合不需要支持 WKWebVIEw, 且网页内容不会太多的场景。

方案二:适用 UIWebVIEw 和 WKWebVIEw,且特别适合 WKWebVIEw。由于采用分页生成截图机制,有效减少内存占用。不过,这个方案存在一个问题:若网页存在 position: fixed 的元素(如网页头部固定的导航栏),该元素会重复出现在生成的长图上。

方案三:适用 UIWebVIEw 和 WKWebVIEw。其中最重要的一步——“把WebVIEw内容打印到pdf” 是由iOS系统实现,所以该方案的性能在理论上是可以得到保障的。不过,这个方案存在一个问题:在把网页内容打印到pdf时,iOS系统获取的 contentSize 比WebVIEw的实际contentSize 要大,从而导致生成的图片在靠近底部的内容部分和实际存在一点差异。具体可以下载运行我的长截图库 SnapshotKit的 Demo,通过其中的 UIWebVIEw 和 WKWebVIEw 截图示例查看具体截图效果。

以上三个方案,总的来说,解决了部分场景的需求,但都不够完美,仍需做进一步的优化。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

总结

以上是内存溢出为你收集整理的iOS使用WebView生成长截图的第3种解决方案全部内容,希望文章能够帮你解决iOS使用WebView生成长截图的第3种解决方案所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/1110113.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-29
下一篇2022-05-29

发表评论

登录后才能评论

评论列表(0条)

    保存