問(wèn)題描述
今天有個(gè)人把他寫的 demo 發(fā)給我調(diào) bug。他遇到的問(wèn)題是,在 UICollectionView 的 headerView 上加了一個(gè)按鈕,但有的 section 的 header 上的按鈕點(diǎn)了有反應(yīng),有的點(diǎn)了沒(méi)反應(yīng)。
我打開(kāi) Xcode 的類 reveal 工具一看,點(diǎn)了沒(méi)反應(yīng)的那幾個(gè) section header 上面都蓋著另外一個(gè)UICollectionReusableView。按鈕被蓋住了,難怪點(diǎn)了沒(méi)反應(yīng)呢。

但為什么會(huì)出現(xiàn)這種情況呢?我們來(lái)看他 header 的注冊(cè)和回調(diào)方法是怎么寫的:
header 的注冊(cè)
[_collectionView registerClass:[Type1HeaderView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:Type1HeaderID];
[_collectionView registerClass:[Type2HeaderView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:Type2HeaderID];
分別用兩個(gè) reuseId 注冊(cè)了兩種 header(為了簡(jiǎn)化,我把命名改了改)。兩種 header 所屬的類都是UICollectionReusableView的子類,沒(méi)什么問(wèn)題。
header 的回調(diào)方法
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
Type1HeaderView *type1HeaderView = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:Type1HeaderID forIndexPath:indexPath];
Type2HeaderView *type2HeaderView = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:Type2HeaderID forIndexPath:indexPath];
if (indexPath.section == 0) {
return type1HeaderView;
} else {
return type2HeaderView;
}
}
return nil;
}
簡(jiǎn)化之后的代碼如上,省去了一些無(wú)關(guān)細(xì)節(jié)。代碼寫得有點(diǎn)隨意,但看著還挺正常的。所以我找了半天才找到問(wèn)題所在。
解決辦法
問(wèn)題就在于那兩句
Type1HeaderView* type1HeaderView = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:Type1HeaderID forIndexPath:indexPath];
Type2HeaderView* type2HeaderView = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:Type2HeaderID forIndexPath:indexPath];
連續(xù)調(diào)用了兩次 dequeueReusableSupplementaryViewOfKind: withReuseIdentifier: forIndexPath:方法,傳的是相同的indexPath。這就導(dǎo)致同一位置上出現(xiàn)了兩個(gè)重疊的 headerView ,一個(gè)蓋住另外一個(gè)。改成這樣:
改正后的回調(diào)方法
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
if (indexPath.section == 0) {
Type1HeaderView *type1HeaderView = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:Type1HeaderID forIndexPath:indexPath];
return type1HeaderView;
} else {
Type2HeaderView *type2HeaderView = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:Type2HeaderID forIndexPath:indexPath];
return type2HeaderView;
}
}
return nil;
}
就一點(diǎn)問(wèn)題都沒(méi)有了。
大概是每調(diào)一次那個(gè)dequeue方法,系統(tǒng)都會(huì)在那個(gè)indexPath的位置上創(chuàng)建一個(gè) header,不管最后 return 什么。我覺(jué)得這個(gè)問(wèn)題還挺奇怪的,因?yàn)椴榱艘幌绿O果官方文檔里并沒(méi)有提到這一點(diǎn),可以算是官方的實(shí)現(xiàn)產(chǎn)生的一個(gè) bug,也是 UICollectionView 的一個(gè)坑了。
結(jié)論
在- (UICollectionReusableView *)collectionView: viewForSupplementaryElementOfKind: atIndexPath:這個(gè)回調(diào)方法的每次執(zhí)行中,要么返回 nil,要么調(diào)用一次且僅一次dequeueReusableSupplementaryViewOfKind: withReuseIdentifier: forIndexPath:,千萬(wàn)不能重復(fù)調(diào)用,否則會(huì)導(dǎo)致詭異的 bug,還不容易找到原因。