自定義UICollectionViewLayout 實現瀑布流
今天研究了一下自定義UICollectionViewLayout。 看了看 官方文檔 ,要自定義UICollectionViewLayout,需要創建一個UICollectionViewLayout的子類。同時,可以通過一下3個方法傳遞布局信息、contentSize、cells的信息等。
一、繼承UICollectionViewLayout,重寫以下方法
1.通過prepareLayout方法來計算預先計算需要提供的布局信息。
2.通過collectionViewContentSize方法來返回contentSize
3.通過layoutAttributesForElementsInRect: 方法來返回每個cell的信息

二、創建UICollectionViewLayoutAttributes,創建的方法有一下三種
1.layoutAttributesForCellWithIndexPath:
2.layoutAttributesForSupplementaryViewOfKind:withIndexPath:
3.layoutAttributesForDecorationViewOfKind:withIndexPath:
其中,layoutAttributesForCellWithIndexPath:方法創建cell的屬性,layoutAttributesForSupplementaryViewOfKind:withIndexPath:創建補充視圖的屬性,如header、footer,layoutAttributesForDecorationViewOfKind:withIndexPath:創建修飾視圖的屬性
基礎知識介紹完了,接下講具體示例 創建一個UICollectionViewLayout的子類WKFlowLayout,
@interface WKFlowLayout : UICollectionViewLayout @property (nonatomic, strong) NSMutableDictionary *layoutInformation; @property (nonatomic) NSInteger maxNumCols; @end static NSUInteger CellWidth = 100; static CGFloat ContentHeight; @implementation WKFlowLayout { NSMutableArray *_yOffsets;//存儲各列的當前offest }
接下來在prepareLayout預先計算布局信息
- (void)prepareLayout { _maxNumCols = 2;//設置為兩列 _yOffsets = [NSMutableArray arrayWithCapacity:_maxNumCols]; for (int i = 0; i < _maxNumCols; i++) { [_yOffsets addObject:@0]; } //初始化cell的寬度 CellWidth = self.collectionView.bounds.size.width / _maxNumCols; //事先創建好UICollectionViewLayoutAttributes _layoutInformation = [NSMutableDictionary dictionary]; NSIndexPath *indexPath; NSInteger numSections = [self.collectionView numberOfSections]; for(NSInteger section = 0; section < numSections; section++){ NSInteger numItems = [self.collectionView numberOfItemsInSection:section]; for(NSInteger item = 0; item < numItems; item++){ indexPath = [NSIndexPath indexPathForItem:item inSection:section]; UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; NSInteger col = indexPath.item % _maxNumCols; WKFlowLayoutDataSource *ds = self.collectionView.dataSource; NSNumber *height = ds.dataSource[indexPath.row]; attributes.frame = CGRectMake(col * CellWidth, [_yOffsets[col] floatValue], CellWidth, [height floatValue]); CGFloat yOffset; yOffset = [_yOffsets[col] floatValue] + [height floatValue]; NSLog(@"yOffset:%f col:%ld", yOffset, (long)col); _yOffsets[col] = @(yOffset); [_layoutInformation setObject:attributes forKey:indexPath]; //計算滾動高度 ContentHeight = MAX(ContentHeight, CGRectGetMaxY(attributes.frame)); } } }
剩下的代碼
- (CGSize)collectionViewContentSize { CGFloat contentWidth = self.collectionView.bounds.size.width; CGSize contentSize = CGSizeMake(contentWidth, ContentHeight); return contentSize; } - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSMutableArray *myAttributes = [NSMutableArray arrayWithCapacity:self.layoutInformation.count]; for(NSString *key in self.layoutInformation.allKeys){ UICollectionViewLayoutAttributes *attributes = [self.layoutInformation objectForKey:key]; if(CGRectIntersectsRect(rect, attributes.frame)){ [myAttributes addObject:attributes]; } } return myAttributes; }
以上就是主要的實現代碼了,需要注意的是,在prepareLayout中預先算出所有的布局信息適用于cell個數小于1000,超過之后在耗時就過長了,用戶體驗不好,同時需要在- (BOOL)shouldInvalidateLayoutForBoundsChange:方法中返回NO,此方法表示不需要再滾動過程中不停的調用prepareLayout
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { return NO; }
示例代碼 再附上官方的 circleLayout