我只是想截個(gè)屏(續(xù))

上兩天寫了一篇《我只是想要截個(gè)屏》的博文, 來描述了在書寫SwViewCapture中遇到的一些坎坷和解決方案。在《我只是想要截個(gè)屏》中并沒有找到針對(duì)WKWebView的全內(nèi)容截圖的相對(duì)完美的解決方案, 只是用一種滾動(dòng)的暴力的方式去截圖然后組裝臨時(shí)解決。

本文主要在上篇文章中做一些粗略的補(bǔ)充, 來描述SwViewCapture中是怎么更好的解決WKWebView的截屏問題, 還有怎么找到這種取巧的解決方案的~

PS: 如果大家想直接看實(shí)現(xiàn)原理, 請(qǐng)?zhí)^幾次失敗嘗試章節(jié)~

幾次失敗嘗試

閱讀過《我只是想要截個(gè)屏》的童鞋們可能都知道, 對(duì)于WKWebView的截圖, 只能使用View的drawViewHierarchyInRect:afterScreenUpdates:方法去獲取截圖。在此前我曾嘗試用如下幾種方案去截圖, 均以失敗收尾。

  1. 將WKWebView的frame拉長(zhǎng)和ContentSize的高度保持一致, 然后截圖
  2. 將WKWebView的frame拉長(zhǎng)和ContentSize的高度一致, 然后通過WKWebView的snapshotViewAfterScreenUpdates獲取的view進(jìn)行截圖
  3. 對(duì)WKWebView內(nèi)部的WKContentView直接截圖
  4. 將WKScrollView對(duì)應(yīng)的Screen進(jìn)行拉伸, 然后對(duì)WKWebView進(jìn)行等價(jià)拉伸, 再截圖
  5. 使用私有API_snapshotRect:intoImageOfWidth:completionHandler

上述第一、二、三種方法是筆者自己腦洞嘗試, 可是截圖要么完全是空白, 要么就只能顯示屏幕區(qū)域的圖。

第四種和第五種是對(duì)WKWebView源碼不了解的窺看后, 進(jìn)行一種投機(jī)取巧嘗試。

既然已經(jīng)實(shí)在找不到解決方案了, 筆者就去官網(wǎng)下載源碼, 希望能夠找到突破口。WKWebView是開源的, 其源碼放置在蘋果官方開源網(wǎng)站http://opensource.apple.com中, 項(xiàng)目名字為WebKit2。

筆者以為下載到源碼了, 至少能夠找到一個(gè)突破口, 在打開工程項(xiàng)目后, 筆者就發(fā)現(xiàn)自己錯(cuò)了, 這個(gè)工程太龐大了。。。

WKWebView的組成筆者尚不熟悉, iOS的WKWebView底層更多的是WebKit的底層實(shí)現(xiàn), 如果徹底從理解去閱讀代碼, 估計(jì)半個(gè)月甚至大半年都不一定讀的完~ 有這個(gè)心思去閱讀代碼, 還不如先去閱讀《WebKit技術(shù)內(nèi)幕》這本書~

筆者自知不可能從閱讀理解源碼進(jìn)行著手, 那就只能直奔要點(diǎn): 關(guān)鍵字跟蹤!筆者一開從WKWebView.mm文件進(jìn)行突破, 去尋找遮蓋關(guān)鍵字unobscured, 從這個(gè)關(guān)鍵字中發(fā)現(xiàn)遮蓋區(qū)域和scrollView的window相關(guān), 因此嘗試第四種方法, 修改window的大小~ 失敗的結(jié)果唯一能夠告訴筆者的就是: 沒有找到遮蓋視圖不渲染的根源!

筆者在第一次尋找關(guān)鍵字失敗后嘗試從snapshot這個(gè)關(guān)鍵字去突破, 結(jié)果發(fā)現(xiàn)了私有API_snapshotRect:intoImageOfWidth:completionHandler。這也是第五種方法的嘗試來源。通過snapshot關(guān)鍵字其實(shí)還發(fā)現(xiàn)了隱藏在WKWebView.mm底下的_takeViewSnapshot方法, 可是該方法返回的對(duì)象是C++對(duì)象, 筆者就沒有從Object-C層級(jí)對(duì)方法進(jìn)行調(diào)用嘗試。

結(jié)合snapshotunobscured兩個(gè)關(guān)鍵字的搜索, 筆者在底層一串跟蹤, 發(fā)現(xiàn)了WebPage、DrawingArea等一系列概念, 筆者偶然間在WebPage的初始化方法中發(fā)現(xiàn)有個(gè)WebPageCreationParameters參數(shù)作為構(gòu)造WebPage的初始參數(shù), 其中包含了如下幾個(gè)參數(shù)

#if PLATFORM(IOS)
    WebCore::FloatSize screenSize;
    WebCore::FloatSize availableScreenSize;
    float textAutosizingWidth;
#endif

通過全局搜索availableScreenSize, 在WebPageProxyIOS.mm源碼中發(fā)現(xiàn), WebPage的屏幕尺寸是根據(jù)WKGetAvailableScreenSize()WKGetScreenSize()獲取的, 核心代碼如下:

FloatSize WebPageProxy::screenSize()
{
    return FloatSize(WKGetScreenSize());
}

FloatSize WebPageProxy::availableScreenSize()
{
    return FloatSize(WKGetAvailableScreenSize());
}

終于有些眉目了, 一全局搜索WKGetAvailableScreenSize崩潰了~ 在WebKit2開源中并沒有這個(gè)方法的定義, 并且無法通過GoogleApple Developer搜索到相關(guān)信息... T_T

最雞血的來了, 跟蹤了幾個(gè)小時(shí), 筆者放棄了... 沒錯(cuò), 筆者直到最后都沒有從源碼中找到解決方案~ =。=

WKWebView截圖方案

雖然沒有通過源碼找到解決方案, 但是通過改變Window的嘗試讓我的腦洞打開, 想到了另外一種和滾動(dòng)截圖很相似的暴力的解決方式。

PS: 滾動(dòng)截圖是筆者在我只想要截個(gè)屏中所描述的暴力解決截圖的方式。實(shí)現(xiàn)方式就是滾動(dòng)一頁截取一頁, 最后組裝成一張長(zhǎng)圖。

筆者想: 既然WKWebView的渲染區(qū)域是屏幕范圍固定的, 那我不滾動(dòng)視圖, 不斷的往上推視圖呢?

不斷往上推視圖的意思就是改變View的origin的y軸, 每截取一張圖片后去上移View的高度(高度等價(jià)于該WKWebView在界面中的顯示范圍)和拉長(zhǎng)WKWebView的總高度, 直到截取到了最后一張圖并組裝。

這個(gè)思路有個(gè)小小的問題, 就是筆者曾經(jīng)嘗試通過放大WKWebView本身去截圖, 但是卻截出一片空白的情況。透過這個(gè)問題可以假設(shè), 我不斷上移y軸并放大高度的最后一張情況和上述有問題的情況完全一致, 可以猜測(cè)這個(gè)方法是無法正確的截取WKWebView的圖的。

筆者用了一種很巧妙的方法去躲避了這個(gè)問題, 就是去截取WKWebView的父視圖, 因?yàn)闊o論WKWebView怎么改變, 通過WKWebView父視圖截圖是可以正確獲取對(duì)應(yīng)的界面的(筆者實(shí)驗(yàn)的)。

通過優(yōu)化后大致的流程如下:

  1. 基于WKWebView的尺寸偽造一個(gè)UIView, 并拉長(zhǎng)至ContentSize高度
  2. 將偽造的UIView作為WKWebView的父視圖
  3. 放置一張大畫布長(zhǎng)度和WKWebView的ContentSize高度一致
  4. 對(duì)父視圖進(jìn)行普通截圖并放置在大畫布中
  5. 將WKWebView的高度上移一個(gè)父視圖的高度
  6. 循環(huán)執(zhí)行步驟3和步驟4直到總高度和WKWebView的ContentSize高度一致
  7. 讀取畫布中的圖像并返回

大致思路如圖:

WKWebView合成示意

思路核心代碼如下:

let containerView  = UIView(frame: self.bounds)

self.removeFromSuperview()
containerView.addSubview(self)


let totalSize = self.scrollView.contentSize
let page      = floorf(Float( totalSize.height / containerView.bounds.height))

UIGraphicsBeginImageContextWithOptions(totalSize, false, UIScreen.mainScreen().scale)

for index in 0...Int(page) {
    // async for, action need package a method

    var splitFrame = CGRectMake(0, CGFloat(index) * containerView.frame.size.height, containerView.bounds.size.width, containerView.frame.size.height) 
    var myFrame = self.frame
        myFrame.origin.y = -(CGFloat(index) * containerView.frame.size.height)
        self.frame = myFrame 
    containerView.drawViewHierarchyInRect(splitFrame, afterScreenUpdates: true)
}

let capturedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext() 

通過這種方式果然可以截取完整的WKWebView, 并且不存在position: fixed;的標(biāo)簽重復(fù)的問題。

<font color='orange'>上述代碼起示意作用, 實(shí)際循環(huán)部分需要等待延遲, 因?yàn)樾枰却齏KWebView在改變frame之后準(zhǔn)備完畢執(zhí)行下一次循環(huán)。</font>

總結(jié)

因?yàn)閃KWebView只能渲染屏幕范圍大小左右的視圖范圍, 因此筆者就利用這個(gè)點(diǎn), 不斷的去改變WKWebView的frame去截圖, 然后組裝成為一張內(nèi)容截圖。通過這種方式可以巧妙的躲避過因?yàn)闈L動(dòng)視圖產(chǎn)生的部分頁面元素重復(fù)的問題。

其實(shí)WKWebView現(xiàn)在在iOS開發(fā)應(yīng)用中并沒有UIWebView廣泛, 做截圖相關(guān)功能的開發(fā)者也可能會(huì)優(yōu)先采用UIWebView最為搭載容器, 但是多多少少本篇文章應(yīng)該還是會(huì)幫助到一些使用WKWebView的先驅(qū)者的~

另: 本文提供的解決方案可能只是眾多解決方案的其中一種, 并且相當(dāng)?shù)暮臅r(shí)也消耗內(nèi)存, 希望大家可以一起想想能否有更優(yōu)的解決方案~ 希望多多交流~ 如果有更好的方案, 跪求Pull Request或者提交issueSwViewCapture。

PS: 鑒于個(gè)人水平有限, 有錯(cuò)誤之處, 請(qǐng)大家及時(shí)指出~ 謝謝~

參考文獻(xiàn)

  1. Apple Open Source - WebKit2
  2. iOS Developer library - WKWebView
  3. 我只是想要截個(gè)屏
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 想必使用iPhone的用戶, 大家都知道按照Home鍵+電源鍵就可以截屏了。 截屏對(duì)于產(chǎn)品經(jīng)理、工程師、設(shè)計(jì)師都比...
    Startry閱讀 8,419評(píng)論 21 44
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,050評(píng)論 4 61
  • 轉(zhuǎn)載:http://www.itdecent.cn/p/aa64fd3dd621 想必使用iPhone的用戶, 大...
    曉飛90閱讀 1,413評(píng)論 0 1
  • 每天都有不順心的事情。每天都不開心,是,家里總來關(guān)系好的大姨,這我知道好,也平時(shí)照應(yīng)照應(yīng)我媽。我高興。但是能不能有...
    雞米兮閱讀 140評(píng)論 0 0
  • 制作一份分的清單,可以是出行前的 開車前的 度假前的。 工作前的,工作中的,完成檢查的。 溝通前的,關(guān)鍵對(duì)話時(shí)的,...
    后知后覺的持續(xù)努力閱讀 322評(píng)論 0 0

友情鏈接更多精彩內(nèi)容