前言
先說問題是什么,那就是當UICollectionView的cell上有webview且在webview中進行過點擊操作后,webview所在cell就無法觸發(fā)UICollectionView的代理方法willDisplay和didEndDisplaying。
demo鏈接: demo
如何發(fā)現(xiàn)
在做分頁支持web活動頁的需求時,QA找了一個全是視頻的鏈接測試。我們的webview封裝VC在willAppear和willDisappear方法中都會告訴前端進入或者離開頁面從而達到離開頁面暫停播放的效果。而分頁的框架是由UICollectionView實現(xiàn)的,其中每個cell的willAppear和willDisappear方法都是由UICollectionView的willDisplay和didEndDisplaying代理方法來實現(xiàn)的。一旦在cell中的webview播放完成視頻,點擊重播后,這個cell就再也不會觸發(fā)上述這兩個方法了,進而導致web中的視頻沒法暫停。
在經(jīng)過排查后發(fā)現(xiàn)這個問題與視頻無關,只要在webview中進行過點擊操作后就會出現(xiàn)。自測發(fā)現(xiàn)在iOS13上無論是UIWebView還是WKWebView,都存在這個問題。QA也在iOS9上發(fā)現(xiàn)相同的問題。
復現(xiàn)
我們使用兩種cell來復現(xiàn)這個問題,一個是有帶webview的cell,一種是普通的cell。我們分別在willDisplay和didEndDisplaying代理方法中加入如下代碼
if cell.isKind(of: WebCell.self) {
print("WebCell:willDisplay")
} else {
print("other:willDisplay")
}
在CollectionView中我們在左右兩側的cell為普通的cell,中間的cell為攜帶webview的cell,如圖1。

在三個cell中切換我們會輸出如下信息
other:willDisplay
WebCell:willDisplay
other:didEndDisplaying
other:willDisplay
WebCell:didEndDisplaying
但一旦在webview中對按鈕進行點擊操作,就會輸出如下信息
other:willDisplay
other:didEndDisplaying
other:willDisplay
other:didEndDisplaying
other:willDisplay
我們可以發(fā)現(xiàn)這些信息中少了webview的信息,也就意味著webcell走不了willDisplay和didEndDisplaying代理方法。
經(jīng)過測試,如果webCell可以被復用,那么當多個webCell滿足無法觸發(fā)代理的條件并經(jīng)過復用后,有且僅有一個webCell無法觸發(fā)willDisplay和didEndDisplaying代理方法。
原因
使用Xcode查看內(nèi)存圖可以發(fā)現(xiàn)正常的持有情況是如圖2的。

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

我們可以發(fā)現(xiàn),webCell轉而被一個私有屬性_firstResponderView持有了。我們可以推測出原因就是這個webCell原本應該在UICollectionView中的數(shù)組里,這樣才可以觸發(fā)willDisplay和didEndDisplaying代理方法,但現(xiàn)在已經(jīng)不再這個數(shù)組里了,也就無法觸發(fā)這兩個代理方法。
解決方案
其實我們看到webCell被私有屬性_firstResponderView持有后,可以想到把keyWindow的firstResponder取出來并執(zhí)行resignFirstResponder()方法。
經(jīng)過試驗發(fā)現(xiàn)我們可以通過如下方法修復這個問題
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()方法即可(實際開發(fā)需要考慮的問題這里暫不考慮)。
對應的OC解決方法為
id first = [[UIApplication sharedApplication].keyWindow performSelector:@selector(firstResponder)];
if (first) {
[(UIView *)first resignFirstResponder];
}
最后附上demo鏈接: demo