前言
先說問題是什么峡继,那就是當UICollectionView的cell上有webview且在webview中進行過點擊操作后冯袍,webview所在cell就無法觸發(fā)UICollectionView的代理方法willDisplay
和didEndDisplaying
。
demo鏈接: demo
如何發(fā)現
在做分頁支持web活動頁的需求時碾牌,QA找了一個全是視頻的鏈接測試康愤。我們的webview封裝VC在willAppear
和willDisappear
方法中都會告訴前端進入或者離開頁面從而達到離開頁面暫停播放的效果。而分頁的框架是由UICollectionView實現的舶吗,其中每個cell的willAppear
和willDisappear
方法都是由UICollectionView的willDisplay
和didEndDisplaying
代理方法來實現的征冷。一旦在cell中的webview播放完成視頻,點擊重播后誓琼,這個cell就再也不會觸發(fā)上述這兩個方法了检激,進而導致web中的視頻沒法暫停。
在經過排查后發(fā)現這個問題與視頻無關腹侣,只要在webview中進行過點擊操作后就會出現叔收。自測發(fā)現在iOS13上無論是UIWebView還是WKWebView,都存在這個問題傲隶。QA也在iOS9上發(fā)現相同的問題饺律。
復現
我們使用兩種cell來復現這個問題,一個是有帶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ā)現這些信息中少了webview的信息,也就意味著webcell走不了willDisplay
和didEndDisplaying
代理方法砸泛。
經過測試十籍,如果webCell可以被復用,那么當多個webCell滿足無法觸發(fā)代理的條件并經過復用后晾嘶,有且僅有一個webCell無法觸發(fā)willDisplay
和didEndDisplaying
代理方法妓雾。
原因
使用Xcode查看內存圖可以發(fā)現正常的持有情況是如圖2的。
但是如果你在webview內進行了點擊操作后垒迂,它的內存圖就變成了圖3械姻。
我們可以發(fā)現,webCell轉而被一個私有屬性_firstResponderView
持有了机断。我們可以推測出原因就是這個webCell原本應該在UICollectionView中的數組里楷拳,這樣才可以觸發(fā)willDisplay
和didEndDisplaying
代理方法,但現在已經不再這個數組里了吏奸,也就無法觸發(fā)這兩個代理方法欢揖。
解決方案
其實我們看到webCell被私有屬性_firstResponderView
持有后,可以想到把keyWindow的firstResponder
取出來并執(zhí)行resignFirstResponder()
方法奋蔚。
經過試驗發(fā)現我們可以通過如下方法修復這個問題
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