瀑布流架構的搭建,瀑布流架構搭建
瀑布流大家都應該熟悉了,現在大部分電商應用中或多或少的都用到瀑布流,它可以吸引使用者的眼球,使使用者不易產生視覺疲勞,蘋果在iOS6中增添了UICollectionView控制項,這個控制項可以說是UITableView的升級版,通過這個控制項我們就能很簡單的做出瀑布流,後面通過自己的封裝可以讓其變成一個小架構,更簡單的應用到我們之後的開發中
最近開通了簡書歡迎大家關注,我會不週期性分享我的iOS開發經驗 點擊關注-->Melody_Zhy
如果想做瀑布流,那麼就要自訂CollectionViewFlowLayout,因為這個類中有一個返回collectionView中所有子控制項的布局屬性(布局屬性中有控制項的frame和索引)的方法
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect { NSArray *arr = [super layoutAttributesForElementsInRect:rect]; for (UICollectionViewLayoutAttributes *attri in arr) { attri.frame = CGRectMake(0, 0, 100, 300); } NSLog(@"%@", arr); return arr;}
這個方法會將collectionView中的所有子控制項的布局屬性計算一次,計算之後就會被緩衝起來,當已經計算過的cell,再次出現時也不會在重複去計算它的尺寸。
注意:如果要進行重新整理資料那麼要記得將之前的布局屬性進行清空,不然會出現布局錯誤
// 把用來裝所有布局屬性的資料做清空處理[self.attrArrM removeAllObjects];
現在定義一個可變數組屬性來儲存一會自己計算的布局屬性中的frame,讓上面的方法返回自己定義的布局屬性
// 用來儲存所有布局屬性的可變數組@property (nonatomic, strong) NSMutableArray *attrArrM;
那麼我們在哪個方法中計算自己定義的布局屬性呢?
有這麼個方法
- (void)prepareLayout {// 要調用父類的prepareLayout [super prepareLayout];}
當collectionView中的所有子控制項即將顯示的時候就會來調用此方法做布局前的準備工作,準備itemSize...等等屬性 同時當布局的屬性發生變化時也會來調用此方法 當重新整理資料之後也會來調用此方法重新做布局前的準備工作
在這個方法中可以通過collectionViewFlowLayout的collectionView的numberOfItemInSection這個方法獲得一組中的所有cell
// 獲得一組中的所有cellNSInteger cellCount = [self.collectionView numberOfItemsInSection:0];
通過for迴圈(迴圈次數為一組中有多少個cell)建立布局屬性,在迴圈中需要計算每一個cell的frame 所以後台要給真實的圖片尺寸(如果不給,自己計算的尺寸會造成圖片閃)
同時顯示時一般都會等比例縮放。
cell寬度的計算cell的寬 = (內容的寬 - (列數 - 1) * 最小間距) / 列數內容的寬 = collectionView的寬 - 組的左邊間距 - 右邊間距
CGFloat contentWidth = self.collectionView.bounds.size.width - self.sectionInset.left - self.sectionInset.right;CGFloat cellW = (contentWidth - (self.columnCount - 1) * self.minimumInteritemSpacing) / self.columnCount;
cell高度的擷取
當要獲得cell高的時候需要通過控制器來獲得模型圖片的高度(高度要和itemW有一定的比例要不圖片會過大 height / width * itemW;),因此需要讓控制器成為我們的代理,在自訂CollectionViewFlowLayout.h檔案中定義協議如下:
#import <UIKit/UIKit.h>@class ZHYCollectionViewFlowLayout;@protocol ZHYCollectionViewFlowLayoutDelegate <NSObject>@required- (CGFloat)waterFallFlowLayoutWithItemHeight:(ZHYCollectionViewFlowLayout *)flowLayout itemW:(CGFloat)itemW CellIndexPath:(NSIndexPath *)indexPath;@end
並且設定代理屬性如下:
@property (weak, nonatomic) id<ZHYCollectionViewFlowLayoutDelegate> delegate
在計算cell高的時候調用代理方法獲得cell的高度
CGFloat cellH = [self.delegate waterFallFlowLayoutWithItemHeight:self itemW:cellW CellIndexPath:indexPath];
為什麼要在代理方法中加入indexPath,因為要通過indexPath來擷取模型cellX的計算
在計算cellX的時候,如果通過 NSInteger col = i % self.columnCount;擷取列號
要注意一個問題:
這樣每次都是按順序柏拉圖片的,那麼如果圖片的尺寸參差不齊有的特別短有的又特別長,不巧的是長的都在一列短的又都在一列這樣會造成美觀性會很差,那麼怎麼解決這個問題呢,我們能不能讓每一行添加完畢後,下一行在添加的時候將第一個添加在上一行高度最短的那面圖片下面呢?答案當然是可以的-_-!
這時候我們就要定義一個可變字典屬性,來儲存每一列的高度,用列號來當字典的key
// 用來記錄每一列的最的高度@property (nonatomic, strong) NSMutableDictionary *colDict;
在prepareLayout方法中給每一列的高度的字典一個預設的高度
for (NSInteger i = 0; i<有幾列; i++) { NSString *str = [NSString stringWithFormat:@"%ld", i]; self.colDict[str] = @(self.sectionInset.top); }
獲得最矮的那一列列號的方法
- (NSString *)minCol { __block NSString *min = @"0"; [self.colDict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { if ([obj floatValue] < [self.colDict[min] floatValue]) { min = key; } }]; return min;}
因此列號為
NSInteger col = [[self minCol] integerValue];
cellX為:
CGFloat cellX = self.sectionInset.left + (cellW + self.minimumInteritemSpacing) * col;
cellY的計算
計算cellY
// 用列號當字典的keyNSString *colStr = [NSString stringWithFormat:@"%ld", col];CGFloat cellY = [self.colDict[colStr] floatValue];// 累計每一列的高度self.colDict[colStr] = @(cellY + cellH + self.minimumLineSpacing);
這樣我們就計算完了每一個cell的X,Y,W,H,我們來設定布局屬性的frame
// 設定屬性的frameattr.frame = CGRectMake(cellX, cellY, cellW, cellH);
同時我們也給尾部視圖添加一個布局屬性,代碼如下
// 建立尾部視圖的布局屬性 // 建立footerview索引 NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0]; // 必須是額外的layoutAttributesForSupplementaryViewOfKind UICollectionViewLayoutAttributes *footerAttr = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind: UICollectionElementKindSectionFooter withIndexPath:indexPath]; footerAttr.frame = CGRectMake(0, [self.colDict[self.maxCol] floatValue] - self.minimumLineSpacing, self.collectionView.bounds.size.width, 50); [self.attrArrM addObject:footerAttr];
這裡要用到最高列,因為尾部視圖要放在cell最下面,獲得最高列索引的方法為:
// 用來取出最高那一列的列號- (NSString *)maxCol { __block NSString *maxCol = @"0"; [self.colDict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { if ([obj floatValue] > [self.colDict[maxCol] floatValue]) { maxCol = key; } }]; return maxCol;}
最後我們通過上面說過的方法把布局屬性返回
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect { return self.attrArrM;}
自訂布局屬性的時候還要注意返回真實的contentSize,代碼如下:
- (CGSize)collectionViewContentSize { return CGSizeMake(0, [self.colDict[self.maxCol] floatValue] + self.footerReferenceSize.height - self.minimumLineSpacing);}
這時基本已經完成了,但如果我想要把這個瀑布流布局做成一個簡單的架構就需要在簡單的實現些初始化方法
在CollectionViewFlowLayout.h還要定義一個一行有幾個cell的屬性,當控制器引用這個類之後可以自行設定
@property (assign, nonatomic) NSInteger columnCount;
提供一些初始化方法,使其預設為一行有3個cell, cell間距及行間距為10,內邊距為頂部20, footerReferenceSize, headerReferenceSize都為50,50
- (instancetype)init{ self = [super init]; if (self) { self.columnCount = 3; self.minimumInteritemSpacing = 10; self.minimumLineSpacing = 10; self.footerReferenceSize = CGSizeMake(50, 50); self.headerReferenceSize = CGSizeMake(50, 50); self.sectionInset = UIEdgeInsetsMake(20, 0, 0, 0); } return self;}
- (instancetype)initWithCoder:(NSCoder *)aDecoder{ self = [super initWithCoder:aDecoder]; if (self) { self.columnCount = 3; self.minimumInteritemSpacing = 10; self.minimumLineSpacing = 10; self.footerReferenceSize = CGSizeMake(50, 50); self.headerReferenceSize = CGSizeMake(50, 50); self.sectionInset = UIEdgeInsetsMake(20, 0, 0, 0); } return self;}
這樣我們簡單的瀑布流架構就搭建成功了~