• 经营者要想“我心换你心”,就要未雨绸缪,让不诚信的诱惑少一点,如此才能在市场中生存下来。反过来说,承担不起亏损就关门歇业,是否也是一种不诚信呢? 2019-09-14
  • 上海市陆家嘴:打造社区多元治理“共同体” 2019-09-14
  • 2017最具体育活力城市排行榜 2019-09-14
  • 李克勤献唱央视“世界杯主题歌” 2018国语专辑筹备中李克勤 世界杯 2019-09-08
  • 回复@看着就想笑:难道公有制社会主义就不能生存发展吗?事实上公有制会生存发展的更好! 2019-09-03
  • 陵川县全力推进乡村振兴战略 2019-09-01
  • 周国平:男女之爱已经很强烈了,但亲子之爱更强烈 2019-09-01
  • 出租广州市荔湾区芳村大道东169号 2019-08-29
  • (原创)取消“份子钱”是出租车改革必须迈出的一步 2019-08-21
  • Facebook公关掌门离职 是应对隐私丑闻干将 2019-08-20
  • 你这纯粹是胡说八道!农民的宅基地,需要交钱才能使用吗?你敢推行这样的政策吗?你也只敢推行对工人的住宅地,要交钱才能使用!这样合理吗?那些交不起房钱的人,难道应该 2019-08-13
  • 西部网(陕西新闻网)硬科技频道 2019-08-13
  • 新华时评:成果不易,更需诚意与信任呵护 2019-08-09
  • 好好的书报亭放没放样,周围堆得一塌糊涂乱七八糟,真正的主人都搓麻将去了。再现代化造型在金钱至上的社会不可能再有相应的雅知气了。 2019-08-09
  • 2018海创会带你领略黑科技产品 2019-08-05
  • 平码独平高手心水论坛:iOS使用WebView生成长截图的第3种解决方案

    平码三中山论坛 www.mslzq.tw 前言

    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 self.bounds.height {
       pageNum = Int(floorf(Float(self.contentSize.height / self.bounds.height)))
      }
    
      let backgroundColor = self.backgroundColor ?? UIColor.white
    
      UIGraphicsBeginImageContextWithOptions(self.contentSize, true, 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 
    

    测试代码:

    // example code
    private 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 UIKit
    import WebKit
    
    /// WebViewPrintPageRenderer: use to print the full content of webview into one image
    internal 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, true, 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, 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)
      }
     }
    }

    WebViewPrintPageRenderer 是该方案的核心类,负责把 WebView组件内容打印到PDF,然后把PDF转换为图片。
    UIWebView 和 WKWebView 则实现对应的扩展。

    测试代码:

    // example code
    private 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 截图示例查看具体截图效果。

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

    总结

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

    为您推荐

    发表评论

    联系我们

    联系我们

    在线咨询: QQ交谈

    地址:中国·贵州·贵阳 友情链接QQ:1034559333 本站QQ群:123733639
    关注微信
    微信扫一扫关注我们

    微信扫一扫关注我们

    关注微博
    返回顶部

    关于本站 || 版权声明 || 免责条款 || 隐私条款 || 平码三中山论坛 || ||

    Copyright??2018 平码三中山论坛 All Rights Reserved·黔ICP备15000265号-2

  • 经营者要想“我心换你心”,就要未雨绸缪,让不诚信的诱惑少一点,如此才能在市场中生存下来。反过来说,承担不起亏损就关门歇业,是否也是一种不诚信呢? 2019-09-14
  • 上海市陆家嘴:打造社区多元治理“共同体” 2019-09-14
  • 2017最具体育活力城市排行榜 2019-09-14
  • 李克勤献唱央视“世界杯主题歌” 2018国语专辑筹备中李克勤 世界杯 2019-09-08
  • 回复@看着就想笑:难道公有制社会主义就不能生存发展吗?事实上公有制会生存发展的更好! 2019-09-03
  • 陵川县全力推进乡村振兴战略 2019-09-01
  • 周国平:男女之爱已经很强烈了,但亲子之爱更强烈 2019-09-01
  • 出租广州市荔湾区芳村大道东169号 2019-08-29
  • (原创)取消“份子钱”是出租车改革必须迈出的一步 2019-08-21
  • Facebook公关掌门离职 是应对隐私丑闻干将 2019-08-20
  • 你这纯粹是胡说八道!农民的宅基地,需要交钱才能使用吗?你敢推行这样的政策吗?你也只敢推行对工人的住宅地,要交钱才能使用!这样合理吗?那些交不起房钱的人,难道应该 2019-08-13
  • 西部网(陕西新闻网)硬科技频道 2019-08-13
  • 新华时评:成果不易,更需诚意与信任呵护 2019-08-09
  • 好好的书报亭放没放样,周围堆得一塌糊涂乱七八糟,真正的主人都搓麻将去了。再现代化造型在金钱至上的社会不可能再有相应的雅知气了。 2019-08-09
  • 2018海创会带你领略黑科技产品 2019-08-05
  • 河南移动掌上营业厅官网 2019今晚六给彩开奖给 河北时时现场开奖结果 快乐十分走势图 老时时的玩法怎么玩 安徽快三推荐号 注册送38.币的捕鱼 江苏7位数走势图 wnba官网中文版 陕西11选5投注