preface
Due to the needs of the project, recently implemented a long screenshot library SnapshotKit. Among them, UIWebView and WKWebView components need to be supported to generate growth screenshots. In order to achieve this feature, consult a lot of information, but also do different novel ideas to try, and finally achieve a new, cheating technical scheme.
The following mainly summarizes the main points and advantages and disadvantages of the implementation of “online existing scheme” and “my new scheme” in terms of the demand of “WebView growth screenshot”.
WebView growth screenshots of the existing scheme
According to the information searched by Google, there are two main schemes for iOS WebView growth screenshots at present:
- Solution 1: Modify Frame and screenshot components
- Scheme 2: Page capture component content, combined with the growth diagram
The specific implementations of plan 1 and Plan 2 are briefly described below.
Solution 1: Modify Frame and screenshot components
The main points of scheme 1 are: modify the frameSize of WebView. scrollView to contentSize, and then take screenshots of the whole WebView. scrollView.
However, this solution only works with the UIWebView component because it loads all the content of the web page at once. The WKWebView component, in order to save memory, loads only the visible portion of the web content — similar to the UITableView component. After modifying the frameSize of WebView. scrollView, the screenshot operation is performed immediately. At this time, WKWebView has not loaded the content of the web page, resulting in the long screenshot generated is blank.
The core code of scheme 1 is as follows:
extension UIScrollView {
public func takeSnapshotOfFullContent(a) -> 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(a)else {
return nil
}
context.setFillColor(backgroundColor.cgColor)
context.setStrokeColor(backgroundColor.cgColor)
self.drawHierarchy(in: self.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext(a)UIGraphicsEndImageContext(a)self.frame = originalFrame
self.contentOffset = originalOffset
return image
}
}
Copy the code
Test code:
// example code
private func takeSnapshotOfUIWebView(a) {
let image = self.webView.scrollView.takeSnapshotOfFullContent()
/ / image processing
}
Copy the code
Scheme 2: Page capture component content, combined with the growth diagram
The main points of scheme 2 are: paging scrolling WebView component content, and then generate paging screenshots, and finally synthesize all paging screenshots into a long graph.
This scheme applies to UIWebView components and WKWebView components.
The core code of scheme 2 is as follows:
extension UIScrollView {
public func takeScreenshotOfFullContent(_ completion: @escaping ((UIImage?) -> Void)) {
// Page draw content to ImageContext
let originalOffset = self.contentOffset
Height
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, true.0)
guard let context = UIGraphicsGetCurrentContext(a)else {
completion(nil)
return
}
context.setFillColor(backgroundColor.cgColor)
context.setStrokeColor(backgroundColor.cgColor)
self.drawScreenshotOfPageContent(0, maxIndex: pageNum) {
let image = UIGraphicsGetImageFromCurrentImageContext(a)UIGraphicsEndImageContext(a)self.contentOffset = originalOffset
completion(image)
}
}
fileprivate func drawScreenshotOfPageContent(_ index: Int, maxIndex: Int, completion: @escaping (a) -> 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()
}
}
}
}
Copy the code
Test code:
// example code
private func takeSnapshotOfUIWebView(a) {
self.uiWebView.scrollView.takeScreenshotOfFullContent { (image) in
/ / image processing}}private func takeSnapshotOfWKWebView(a) {
self.wkWebView.scrollView.takeScreenshotOfFullContent { (image) in
/ / image processing}}Copy the code
WebView generates a new solution for growing screenshots
Besides plan 1 and plan 2, are there any new plans?
The answer is yes plus yes and yes.
The key to this new solution: iOS WebView printing.
IOS supports printing WebView content to PDF files. With this feature, the design of the new solution is as follows:
-
Print the contents of the WebView component to a SINGLE PDF page
-
Convert PDF to picture
The core code of the new solution is as follows:
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
// Generate the PrintPageRenderer instance
///
/// - Parameters:
/// -formatter: WebView viewPrintFormatter
/// - contentSize: WebView contentSize
required init(formatter: UIPrintFormatter, contentSize: CGSize) {
self.formatter = formatter
self.contentSize = contentSize
super.init(a)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(a) -> CGPDFPage? {
let data = NSMutableData(a)UIGraphicsBeginPDFContextToData(data, self.paperRect, nil)
self.prepare(forDrawingPages: NSMakeRange(0.1))
let bounds = UIGraphicsGetPDFContextBounds(a)UIGraphicsBeginPDFPage(a)self.drawPage(at: 0.in: bounds)
UIGraphicsEndPDFContext(a)let cfData = data as CFData
guard let provider = CGDataProvider.init(data: cfData) else {
return nil
}
let pdfDocument = CGPDFDocument.init(provider)
letpdfPage = 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(a)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(a)UIGraphicsEndImageContext(a)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(a) -> 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)
}
}
}
Copy the code
WebViewPrintPageRenderer, the core class of the solution, is responsible for printing WebView component content to a PDF and then converting the PDF to an image.
UIWebView and WKWebView implement corresponding extensions.
Test code:
// example code
private func takeSnapshotOfUIWebView(a) {
self.uiWebView.scrollView.takeScreenshotOfFullContent { (image) in
/ / image processing}}private func takeSnapshotOfWKWebView(a) {
self.wkWebView.scrollView.takeScreenshotOfFullContent { (image) in
/ / image processing}}Copy the code
Comparison of advantages and disadvantages of three technical schemes
So, what are the advantages and disadvantages of these three technical solutions, and what scenarios are they applicable to?
- Plan 1: Applicable only
UIWebView
; If the page has a lot of content, it will take up too much memory when creating screenshots. Therefore, the scheme is only suitable without supportWKWebView
, and the web content will not be too many scenes. - Plan two: Application
UIWebView
和WKWebView
And it is particularly suitableWKWebView
. Due to the use of paging screenshot generation mechanism, effectively reduce memory consumption. However, there is a problem with this solution: if the web page existsposition: fixed
Element (such as a fixed navigation bar at the head of a web page) that appears repeatedly on the generated long graph. - Plan three: Application
UIWebView
和WKWebView
. The most important step — “print WebView content to PDF” is implemented by iOS system, so the performance of this solution can be guaranteed in theory. However, there is a problem with this solution: when the web content is printed to a PDF, the iOS system gets itcontentSize
More realistic than WebViewcontentSize
Large, resulting in a slightly different image near the bottom of the content. Specific can download run my long cut gallerySnapshotKitThe Demo, through whichUIWebView
和WKWebView
Screenshot Example View the screenshot effect.
The above three schemes, on the whole, have solved the requirements of some scenes, but they are not perfect enough and still need to be further optimized.