在開發(fā)中我們難免會(huì)用到UICollectionView子刮,一般常規(guī)用法是沒有任何問題的,但是诱建,比如在用UICollectionView實(shí)現(xiàn)瀑布流效果時(shí)钮蛛,自定義每個(gè)cell的frame屬性的時(shí)候就會(huì)出現(xiàn)在滑動(dòng)過程中有些cell一會(huì)顯示一會(huì)消失的奇葩問題(特別是cell較多的時(shí)候,總會(huì)滑動(dòng)到某個(gè)地方的時(shí)候出現(xiàn)cell突然消失的效果)受神。更奇葩的是抛猖,有的情況是在6s上顯示正常,在5s上會(huì)出現(xiàn)一會(huì)消失一會(huì)顯示鼻听。
比如在我的demo中是這樣子的:
什么Bug?
在網(wǎng)上搜索關(guān)鍵字cell disappearing in UICollection view
或UICollectionView some cell not appear
或UICollectionView滾動(dòng)的時(shí)候cell消失
财著,你會(huì)發(fā)現(xiàn)網(wǎng)上有很多人遇到過這種問題,下面附上各大論壇上的圖和鏈接:
蘋果官方開發(fā)者論壇的Problem of cell disappearing in UICollection view in ios 10 only
來自stackoverflow的UICollectionView's cell disappearing
來自segmentfault的UICollectionView滾動(dòng)的時(shí)候會(huì)出現(xiàn)cell消失的情況
有人說通過將UICollectionView的bounces屬性設(shè)置為NO撑碴,有人說這是UICollectionView的bug(提到蘋果官方論壇也沒人回復(fù))撑教,有人推薦使用PSTCollectionView這個(gè)輪子(用UIScrollView的子類實(shí)現(xiàn)類似UICollectionView的效果)。
下面先來看看造成cell一會(huì)顯示一會(huì)消失的效果的主要代碼:
- (void)prepareLayout {
[super prepareLayout];
}
#pragma mark - CollectionView的滾動(dòng)范圍
- (CGSize)collectionViewContentSize
{
CGFloat width = self.collectionView.frame.size.width;
CGFloat maxY = [self maxOrignYInSection:_framesArray.count - 1];
return CGSizeMake(width, maxY + _rowHeight + self.sectionInset.bottom);
}
#pragma mark - 所有cell和view的布局屬性
//sectionheader sectionfooter decorationview collectionviewcell的屬性都會(huì)走這個(gè)方法
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *tmpArray = [super layoutAttributesForElementsInRect:rect];
NSMutableArray *array = [NSMutableArray arrayWithCapacity:tmpArray.count];
for(NSInteger i = 0; i < tmpArray.count; i++){
UICollectionViewLayoutAttributes *attrs = [tmpArray objectAtIndex:i];
UICollectionElementCategory category = attrs.representedElementCategory;
if(category == UICollectionElementCategoryCell){
[array addObject:[self layoutAttributesForItemAtIndexPath:attrs.indexPath]];
}else if (category == UICollectionElementCategorySupplementaryView){
UICollectionViewLayoutAttributes *theAttrs = [self layoutAttributesForSupplementaryViewOfKind:attrs.representedElementKind
atIndexPath:attrs.indexPath];
[array addObject:theAttrs];
}
}
return array;
}
詳細(xì)復(fù)現(xiàn)代碼在ReappearBugCode
分析代碼醉拓,尋找Bug
首先伟姐,我們這里是用UICollectionView實(shí)現(xiàn)一個(gè)高度固定,寬度不固定的瀑布流效果廉嚼,每個(gè)cell的寬度根據(jù)文字內(nèi)容計(jì)算的玫镐,每一行顯示不全的時(shí)候自動(dòng)換行,在cell展示的時(shí)候通過獲取cell對(duì)應(yīng)的布局屬性來把這個(gè)cell展示在指定的位置上怠噪。
其次恐似,在cell全部顯示的情況下觀察,cell的frame全部是正確的傍念,這就說明我們代碼計(jì)算每一個(gè)cell的布局屬性是沒有問題的矫夷。并且UICollectionView的可滑動(dòng)范圍contentSize的計(jì)算也是沒有問題的。
最后憋槐,這些一會(huì)顯示一會(huì)消失的cell是在UICollectionView滑動(dòng)到某個(gè)區(qū)域時(shí)出現(xiàn)的双藕,這就說明在這個(gè)區(qū)域內(nèi)的cell布局獲取的有問題(計(jì)算沒問題)。
我們知道自定義的UICollectionViewLayout時(shí)必須實(shí)現(xiàn)并且會(huì)按順序執(zhí)行的方法如下:
- (void)prepareLayout;//step 1
- (CGSize)collectionViewContentSize;//step 2
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;//step 3
由上面的分析可見阳仔,問題應(yīng)該出在layoutAttributesForElementsInRect:
方法中忧陪,我們?cè)诳煲瑒?dòng)到出現(xiàn)異常的區(qū)域時(shí)在這個(gè)方法處加個(gè)斷點(diǎn)。當(dāng)滑動(dòng)到出現(xiàn)異常的區(qū)域時(shí),看到tmpArray為空了嘶摊,說明問題確實(shí)出在了這里延蟹。
因?yàn)槲覀円呀?jīng)對(duì)每個(gè)Cell都自定義了布局,調(diào)用[super layoutAttributesForElementsInRect:rect]
返回的布局屬性的集合并不是我們想要的叶堆。所以在這里阱飘,我們需要在這里獲取UICollectionView當(dāng)前可見的返回,然后自己返回當(dāng)前處在該區(qū)域內(nèi)的cell的布局屬性集合虱颗。
修改代碼沥匈,解決Bug
解決思路和步驟:
- 在
prepareLayout
方法中計(jì)算所有cell的frame并緩存起來,可提高UICollectionView滑動(dòng)的流暢性 - 在
collectionViewContentSize
方法中根據(jù)上面計(jì)算出來的frame返回UICollectionView可滑動(dòng)的范圍 - 在
layoutAttributesForElementsInRect:
方法中先拿到UICollectionView當(dāng)前可見范圍忘渔,然后遍歷上面計(jì)算的frame高帖,判斷哪些cell或header應(yīng)該展示在該區(qū)域內(nèi),把這些cell和header的布局屬性放到一個(gè)數(shù)組中返回辨萍。
修改后的主要代碼:
#pragma mark - 重寫父類的方法棋恼,實(shí)現(xiàn)瀑布流布局
//step1
- (void)prepareLayout {
[super prepareLayout];
[self calculateFrames];
}
#pragma mark - CollectionView的滾動(dòng)范圍
//step2
- (CGSize)collectionViewContentSize
{
CGFloat width = self.collectionView.frame.size.width;
return CGSizeMake(width, _contentHeight);
}
#pragma mark - 所有cell和view的布局屬性
//sectionheader sectionfooter decorationview collectionviewcell的屬性都會(huì)走這個(gè)方法
//step3
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray *attributesArray = [NSMutableArray array];
CGPoint offset = self.collectionView.contentOffset;
CGRect visibleRect = CGRectMake(0, offset.y, CGRectGetWidth(self.collectionView.frame), CGRectGetHeight(self.collectionView.frame));
for(NSInteger section = 0; section < _framesArray.count; section++){
NSArray *currentSectionFrames = _framesArray[section];
for(NSInteger row = 0; row < currentSectionFrames.count; row++){
CGRect currentFrame = [currentSectionFrames[row] CGRectValue];
NSIndexPath *currentIndexPath = [NSIndexPath indexPathForRow:row inSection:section];
if(currentFrame.origin.y + currentFrame.size.height >= visibleRect.origin.y &&
currentFrame.origin.y <= visibleRect.origin.y + visibleRect.size.height){
//first section header should show
if(row == 0 && section == 0){
UICollectionViewLayoutAttributes *headerAttr = [[self layoutAttributesForSupplementaryViewOfKind:@"UICollectionElementKindSectionHeader"
atIndexPath:currentIndexPath] copy];
CGRect frame = headerAttr.frame;
frame.origin.y = 0;
headerAttr.frame = frame;
[attributesArray addObject:headerAttr];
}
//cell should show
UICollectionViewLayoutAttributes *cellAttrs = [[self layoutAttributesForItemAtIndexPath:currentIndexPath] copy];
cellAttrs.frame = currentFrame;
[attributesArray addObject:cellAttrs];
//next section header should show
if(row == currentSectionFrames.count - 1 && section + 1 < _framesArray.count &&
currentFrame.origin.y + currentFrame.size.height + self.sectionInset.bottom < visibleRect.origin.y + visibleRect.size.height){
UICollectionViewLayoutAttributes *headerAttr = [[self layoutAttributesForSupplementaryViewOfKind:@"UICollectionElementKindSectionHeader"
atIndexPath:[NSIndexPath indexPathForRow:0 inSection:section + 1]] copy];
CGFloat y = [self contentHeightInSection:section];
CGRect frame = headerAttr.frame;
frame.origin.y = y;
headerAttr.frame = frame;
[attributesArray addObject:headerAttr];
}
}
}
}
return attributesArray;
}
修改后的效果:
詳細(xì)代碼見:YLTagsChooser 如果大家有更好的解決辦法,歡迎反饋锈玉。