震驚!cell上的webview播放視頻,竟不走代理方法?

前言

先說(shuō)問題是什么,那就是當(dāng)UICollectionView的cell上有webview且在webview中進(jìn)行過(guò)點(diǎn)擊操作后,webview所在cell就無(wú)法觸發(fā)UICollectionView的代理方法willDisplaydidEndDisplaying。

demo鏈接: demo

如何發(fā)現(xiàn)

在做分頁(yè)支持web活動(dòng)頁(yè)的需求時(shí),QA找了一個(gè)全是視頻的鏈接測(cè)試。我們的webview封裝VC在willAppearwillDisappear方法中都會(huì)告訴前端進(jìn)入或者離開頁(yè)面從而達(dá)到離開頁(yè)面暫停播放的效果。而分頁(yè)的框架是由UICollectionView實(shí)現(xiàn)的,其中每個(gè)cell的willAppearwillDisappear方法都是由UICollectionView的willDisplaydidEndDisplaying代理方法來(lái)實(shí)現(xiàn)的。一旦在cell中的webview播放完成視頻,點(diǎn)擊重播后,這個(gè)cell就再也不會(huì)觸發(fā)上述這兩個(gè)方法了,進(jìn)而導(dǎo)致web中的視頻沒法暫停。

在經(jīng)過(guò)排查后發(fā)現(xiàn)這個(gè)問題與視頻無(wú)關(guān),只要在webview中進(jìn)行過(guò)點(diǎn)擊操作后就會(huì)出現(xiàn)。自測(cè)發(fā)現(xiàn)在iOS13上無(wú)論是UIWebView還是WKWebView,都存在這個(gè)問題。QA也在iOS9上發(fā)現(xiàn)相同的問題。

復(fù)現(xiàn)

我們使用兩種cell來(lái)復(fù)現(xiàn)這個(gè)問題,一個(gè)是有帶webview的cell,一種是普通的cell。我們分別在willDisplaydidEndDisplaying代理方法中加入如下代碼

        if cell.isKind(of: WebCell.self) {
            print("WebCell:willDisplay")
        } else {
            print("other:willDisplay")
        }

在CollectionView中我們?cè)谧笥覂蓚?cè)的cell為普通的cell,中間的cell為攜帶webview的cell,如圖1。

圖1 PageView的三頁(yè)

在三個(gè)cell中切換我們會(huì)輸出如下信息

other:willDisplay
WebCell:willDisplay
other:didEndDisplaying
other:willDisplay
WebCell:didEndDisplaying

但一旦在webview中對(duì)按鈕進(jìn)行點(diǎn)擊操作,就會(huì)輸出如下信息

other:willDisplay
other:didEndDisplaying
other:willDisplay
other:didEndDisplaying
other:willDisplay

我們可以發(fā)現(xiàn)這些信息中少了webview的信息,也就意味著webcell走不了willDisplaydidEndDisplaying代理方法。

經(jīng)過(guò)測(cè)試,如果webCell可以被復(fù)用,那么當(dāng)多個(gè)webCell滿足無(wú)法觸發(fā)代理的條件并經(jīng)過(guò)復(fù)用后,有且僅有一個(gè)webCell無(wú)法觸發(fā)willDisplaydidEndDisplaying代理方法。

原因

使用Xcode查看內(nèi)存圖可以發(fā)現(xiàn)正常的持有情況是如圖2的。

圖2 正常內(nèi)存圖

但是如果你在webview內(nèi)進(jìn)行了點(diǎn)擊操作后,它的內(nèi)存圖就變成了圖3。

圖3

我們可以發(fā)現(xiàn),webCell轉(zhuǎn)而被一個(gè)私有屬性_firstResponderView持有了。我們可以推測(cè)出原因就是這個(gè)webCell原本應(yīng)該在UICollectionView中的數(shù)組里,這樣才可以觸發(fā)willDisplaydidEndDisplaying代理方法,但現(xiàn)在已經(jīng)不再這個(gè)數(shù)組里了,也就無(wú)法觸發(fā)這兩個(gè)代理方法。

解決方案

其實(shí)我們看到webCell被私有屬性_firstResponderView持有后,可以想到把keyWindow的firstResponder取出來(lái)并執(zhí)行resignFirstResponder()方法。

經(jīng)過(guò)試驗(yàn)發(fā)現(xiàn)我們可以通過(guò)如下方法修復(fù)這個(gè)問題

    func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
//        解決方案
        let sel = Selector("firstResponder")
        let fr = UIApplication.shared.keyWindow?.perform(sel)
        if let view = fr?.takeRetainedValue() as? UIView {
            view.resignFirstResponder()
        }
        if cell.isKind(of: WebCell.self) {
            print("WebCell:willDisplay")
        } else {
            print("other:willDisplay")
        }
    }

willDisplay代理中取出firstResponder,執(zhí)行resignFirstResponder()方法即可(實(shí)際開發(fā)需要考慮的問題這里暫不考慮)。

對(duì)應(yīng)的OC解決方法為

    id first = [[UIApplication sharedApplication].keyWindow performSelector:@selector(firstResponder)];
    if (first) {
        [(UIView *)first resignFirstResponder];
    }

最后附上demo鏈接: demo

?著作權(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)容

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