UICollectionView 02 - 自定義局篇
一,UICollectionViewLayout布局的具體思路:
-
設置
itemSzie屬性,它定義了每一個item的大小。在一個示例中通過設置layout的itemSize屬性全局的設置了cell的尺寸。- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
-
設置間隔
間隔可以指定item之間的間隔和每一行之間的間隔。間隔和itemSzie一樣,既有全局屬性,也可以對每一個item設定:
@property (nonatomic) CGFloat minimumLineSpacing; @property (nonatomic) CGFloat minimumInteritemSpacing; - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section; - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section;
-
設定滾動方向
typedef NS_ENUM(NSInteger, UICollectionViewScrollDirection) { UICollectionViewScrollDirectionVertical, UICollectionViewScrollDirectionHorizontal };
-
設置Header和Footer的尺寸
設置Header和Footer的尺寸也分為全局和局部。在這里需要注意滾動的方向,滾動方向不同,header和footer的寬度和高度只有一個會起作用。垂直滾動時section間寬度為尺寸的高。
@property (nonatomic) CGSize headerReferenceSize; @property (nonatomic) CGSize footerReferenceSize; - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section; - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section;
-
設置內邊距
@property (nonatomic) UIEdgeInsets sectionInset; - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
二,用UICollectionView實現瀑布流
實現瀑布流的方式有幾種,但是比較簡單的是通過UICollectionView,因為collectionView自己會實現cell的循環利用,所以自己不用實現循環利用的機制。瀑布就最重要的就是布局,要選取最短的那一列來排布,保證每一列之間的間距不會太大。
實現步驟
-
自定義繼承自UICollectionViewLayout的子類來進行實現布局
- 調用
- (void)prepareLayout進行初始化 - 重載
- (CGSize)collectionViewContentSize返回內容的大小 - 重載
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect方法返回rect中所有元素的布局屬性,返回的是一個數組 - 重載
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath方法返回對應的indexPath的位置的cell的布局屬性。 - 重載
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;方法返回對應indexPath的位置的追加視圖的布局屬性,如果沒有就不用重載 - 重載
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString*)elementKind atIndexPath:(NSIndexPath *)indexPath;方法返回對應indexPath的位置的裝飾視圖的布局屬性,如果沒有也不需要重載 - 重載
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;當邊界發生改變時,是否應該刷新。
- 調用
三,自定義UICollectionViewLayout布局的示例代碼
用代理來實現對item的布局屬性的控制
-
.h文件
#import <UIKit/UIKit.h> @class LMHWaterFallLayout; @protocol LMHWaterFallLayoutDeleaget<NSObject> @required /** * 每個item的高度 */ - (CGFloat)waterFallLayout:(LMHWaterFallLayout *)waterFallLayout heightForItemAtIndexPath:(NSUInteger)indexPath itemWidth:(CGFloat)itemWidth; @optional /** * 有多少列 */ - (NSUInteger)columnCountInWaterFallLayout:(LMHWaterFallLayout *)waterFallLayout; /** * 每列之間的間距 */ - (CGFloat)columnMarginInWaterFallLayout:(LMHWaterFallLayout *)waterFallLayout; /** * 每行之間的間距 */ - (CGFloat)rowMarginInWaterFallLayout:(LMHWaterFallLayout *)waterFallLayout; /** * 每個item的內邊距 */ - (UIEdgeInsets)edgeInsetdInWaterFallLayout:(LMHWaterFallLayout *)waterFallLayout; @end @interface LMHWaterFallLayout : UICollectionViewLayout /** 代理 */ @property (nonatomic, weak) id<LMHWaterFallLayoutDeleaget> delegate; @end
-
.m文件
#import "LMHWaterFallLayout.h" /** 默認的列數 */ static const CGFloat LMHDefaultColunmCount = 3; /** 每一列之間的間距 */ static const CGFloat LMHDefaultColunmMargin = 10; /** 每一行之間的間距 */ static const CGFloat LMHDefaultRowMargin = 10; /** 內邊距 */ static const UIEdgeInsets LMHDefaultEdgeInsets = {10,10,10,10}; @interface LMHWaterFallLayout() /** 存放所有的布局屬性 */ @property (nonatomic, strong) NSMutableArray * attrsArr; /** 存放所有列的當前高度 */ @property (nonatomic, strong) NSMutableArray *columnHeights; /** 內容的高度 */ @property (nonatomic, assign) CGFloat contentHeight; - (NSUInteger)colunmCount; - (CGFloat)columnMargin; - (CGFloat)rowMargin; - (UIEdgeInsets)edgeInsets; @end @implementation LMHWaterFallLayout #pragma mark 懶加載 - (NSMutableArray *)attrsArr{ if (!_attrsArr) { _attrsArr = [NSMutableArray array]; } return _attrsArr; } - (NSMutableArray *)columnHeights{ if (!_columnHeights) { _columnHeights = [NSMutableArray array]; } return _columnHeights; } #pragma mark - 數據處理 /** * 列數 */ - (NSUInteger)colunmCount{ if ([self.delegate respondsToSelector:@selector(columnCountInWaterFallLayout:)]) { return [self.delegate columnCountInWaterFallLayout:self]; }else{ return LMHDefaultColunmCount; } } /** * 列間距 */ - (CGFloat)columnMargin{ if ([self.delegate respondsToSelector:@selector(columnMarginInWaterFallLayout:)]) { return [self.delegate columnMarginInWaterFallLayout:self]; }else{ return LMHDefaultColunmMargin; } } /** * 行間距 */ - (CGFloat)rowMargin{ if ([self.delegate respondsToSelector:@selector(rowMarginInWaterFallLayout:)]) { return [self.delegate rowMarginInWaterFallLayout:self]; }else{ return LMHDefaultRowMargin; } } /** * item的內邊距 */ - (UIEdgeInsets)edgeInsets{ if ([self.delegate respondsToSelector:@selector(edgeInsetdInWaterFallLayout:)]) { return [self.delegate edgeInsetdInWaterFallLayout:self]; }else{ return LMHDefaultEdgeInsets; } } /** * 初始化 */ - (void)prepareLayout{ [super prepareLayout]; self.contentHeight = 0; // 清除之前計算的所有高度 [self.columnHeights removeAllObjects]; // 設置每一列默認的高度 for (NSInteger i = 0; i < LMHDefaultColunmCount ; i ++) { [self.columnHeights addObject:@(LMHDefaultEdgeInsets.top)]; } // 清楚之前所有的布局屬性 [self.attrsArr removeAllObjects]; // 開始創建每一個cell對應的布局屬性 NSInteger count = [self.collectionView numberOfItemsInSection:0]; for (int i = 0; i < count; i++) { // 創建位置 NSIndexPath * indexPath = [NSIndexPath indexPathForItem:i inSection:0]; // 獲取indexPath位置上cell對應的布局屬性 UICollectionViewLayoutAttributes * attrs = [self layoutAttributesForItemAtIndexPath:indexPath]; [self.attrsArr addObject:attrs]; } } /** * 返回indexPath位置cell對應的布局屬性 */ - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{ // 創建布局屬性 UICollectionViewLayoutAttributes * attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; //collectionView的寬度 CGFloat collectionViewW = self.collectionView.frame.size.width; // 設置布局屬性的frame CGFloat cellW = (collectionViewW - self.edgeInsets.left - self.edgeInsets.right - (self.colunmCount - 1) * self.columnMargin) / self.colunmCount; CGFloat cellH = [self.delegate waterFallLayout:self heightForItemAtIndexPath:indexPath.item itemWidth:cellW]; // 找出最短的那一列 NSInteger destColumn = 0; CGFloat minColumnHeight = [self.columnHeights[0] doubleValue]; for (int i = 1; i < LMHDefaultColunmCount; i++) { // 取得第i列的高度 CGFloat columnHeight = [self.columnHeights[i] doubleValue]; if (minColumnHeight > columnHeight) { minColumnHeight = columnHeight; destColumn = i; } } CGFloat cellX = self.edgeInsets.left + destColumn * (cellW + self.columnMargin); CGFloat cellY = minColumnHeight; if (cellY != self.edgeInsets.top) { cellY += self.rowMargin; } attrs.frame = CGRectMake(cellX, cellY, cellW, cellH); // 更新最短那一列的高度 self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame)); // 記錄內容的高度 - 即最長那一列的高度 CGFloat maxColumnHeight = [self.columnHeights[destColumn] doubleValue]; if (self.contentHeight < maxColumnHeight) { self.contentHeight = maxColumnHeight; } return attrs; } /** * 決定cell的布局屬性 */ - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{ return self.attrsArr; } /** * 內容的高度 */ - (CGSize)collectionViewContentSize{ return CGSizeMake(0, self.contentHeight + self.edgeInsets.bottom); }
- 結果
![]()
四,demo及注意
1.瀑布流中自定的cell時,不能使用純frame布局,這是因為cell存在復用的問題。如果在初始化方法中直接使用frame布局,就會在復用cell時,造成cell內容與cell的控件存在混亂。
2.demo

浙公網安備 33010602011771號