以前使用UIWebview時,想截取整個頁面,可以調整內部scrollView的frame,之后調用 scrollView的layer的 render 方法,很方便。
但是在WKWebView上,行不通。
我覺得以前的UIWebview其實是把整個頁面都渲染在內存中,只是我們看不到。而WKWebView為了優化內存,只渲染WKWebView的Frame大小的內容。
所以想用WKWebview截取整個頁面,必須放大WKWebview的frame。
webView.frame = CGRect(x: 0, y: 0, width: webView.scrollView.frame.size.width, height: webView.scrollView.contentSize.height);
改變了frame之后,我們就可以利用scrollView.layer.render 去渲染整個頁面了。
但是這時候又出現了另一個問題: 渲染網頁是需要時間的,把webview的frame擴大后,我們不知道什么時候,系統完成了渲染。比如下面這個例子:
@IBAction func takeScreenshot(){ webView.frame = CGRect(x: 0, y: 0, width: webView.scrollView.frame.size.width, height: webView.scrollView.contentSize.height); let scrollView = self.webView.scrollView UIGraphicsBeginImageContextWithOptions(self.webView.scrollView.contentSize,false, UIScreen.main.scale) scrollView.layer.render(in: UIGraphicsGetCurrentContext()!) let image = UIGraphicsGetImageFromCurrentImageContext() let pngData = UIImagePNGRepresentation(image!); let dstPath = NSHomeDirectory()+"/Documents/test.png" let dstUrl = URL(fileURLWithPath: dstPath) do{ try pngData?.write(to: dstUrl, options: .atomicWrite) }catch{ } print("dest is %@",dstUrl); UIGraphicsEndImageContext() }
由於立即調用了截圖函數,webView沒有足夠的時間渲染,只多渲染了一小部分。
之后,我用下面的代碼進行測試,注意,這里延時了0.3s,給了webview一定的渲染時間:
@IBAction func takeScreenshot(){ webView.frame = CGRect(x: 0, y: 0, width: webView.scrollView.frame.size.width, height: webView.scrollView.contentSize.height); DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+0.3) { let scrollView = self.webView.scrollView UIGraphicsBeginImageContextWithOptions(self.webView.scrollView.contentSize,false, UIScreen.main.scale) scrollView.layer.render(in: UIGraphicsGetCurrentContext()!) let image = UIGraphicsGetImageFromCurrentImageContext() let pngData = UIImagePNGRepresentation(image!); let dstPath = NSHomeDirectory()+"/Documents/test.png" let dstUrl = URL(fileURLWithPath: dstPath) do{ try pngData?.write(to: dstUrl, options: .atomicWrite) }catch{ } print("dest is %@",dstUrl); UIGraphicsEndImageContext() } }
下面是結果的截圖,一切正常:
那么,如果網頁更長,0.3秒一定也不夠用,我們怎么知道該延時多少呢?
這時候我又發現了一個函數,是屬於UIView的,drawHierarchy,根據api描述,第二個參數好像和渲染有關,能不能解決我們的問題呢,繼續測試:
@IBAction func takeScreenshot(){ webView.frame = CGRect(x: 0, y: 0, width: webView.scrollView.frame.size.width, height: webView.scrollView.contentSize.height); let scrollView = self.webView.scrollView UIGraphicsBeginImageContextWithOptions(self.webView.scrollView.contentSize,false, UIScreen.main.scale) self.webView.drawHierarchy(in: CGRect(x: 0, y: 0, width: self.webView.scrollView.frame.size.width, height: self.webView.scrollView.contentSize.height), afterScreenUpdates: true) let image = UIGraphicsGetImageFromCurrentImageContext() let pngData = UIImagePNGRepresentation(image!); let dstPath = NSHomeDirectory()+"/Documents/test.png" let dstUrl = URL(fileURLWithPath: dstPath) do{ try pngData?.write(to: dstUrl, options: .atomicWrite) }catch{ } print("dest is %@",dstUrl); UIGraphicsEndImageContext() }
結果還是不行,效果和使用layer的render方法一樣!看來afterScreenUpdates這個參數跟網頁的渲染無關了。
那么把Webview frame直接擴大為html內容的大小並截圖的方式其實是很有問題的,截圖時機不好掌握, 內存和cpu的占用也會很大。
這里要推薦一個github上的項目,https://github.com/startry/SwViewCapture, 它的解決思路如下:
1. 截圖時機的掌握:每次通過調整視圖frame,只渲染一屏的截圖,速度很快,只需稍為延遲,即可保證完美截圖。
2.內存和cpu:由於每次只處理一屏幕的截圖,內容很少,對cpu和內存的沖擊都很小。
下面貼出其中的關鍵代碼:
fileprivate func swContentPageDraw (_ targetView: UIView, index: Int, maxIndex: Int, drawCallback: @escaping () -> Void) { // set up split frame of super view let splitFrame = CGRect(x: 0, y: CGFloat(index) * targetView.frame.size.height, width: targetView.bounds.size.width, height: targetView.frame.size.height) // set up webview frame var myFrame = self.frame myFrame.origin.y = -(CGFloat(index) * targetView.frame.size.height) self.frame = myFrame DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(0.3 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) { () -> Void in targetView.drawHierarchy(in: splitFrame, afterScreenUpdates: true) if index < maxIndex { self.swContentPageDraw(targetView, index: index + 1, maxIndex: maxIndex, drawCallback: drawCallback) }else{ drawCallback() } } }