瀑布流你們都應該熟悉了,如今大部分電商應用中或多或少的都用到瀑布流,它能夠吸引用戶的眼球,使用戶不易產生視覺疲勞,蘋果在iOS6中增添了UICollectionView控件,這個控件能夠說是UITableView的升級版,經過這個控件咱們就能很簡單的作出瀑布流,後面經過本身的封裝可讓其變成一個小框架,更簡單的應用到咱們以後的開發中數組
最近開通了簡書歡迎你們關注,我會不按期的分享個人iOS開發經驗 點擊關注-->Melody_Zhy緩存
框架
- (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,讓上面的方法返回本身定義的佈局屬性ui
// 用來保存全部佈局屬性的可變數組 @property (nonatomic, strong) NSMutableArray *attrArrM;
那麼咱們在哪一個方法中計算本身定義的佈局屬性呢?atom
有這麼個方法 spa
- (void)prepareLayout { // 要調用父類的prepareLayout [super prepareLayout]; }
在這個方法中能夠經過collectionViewFlowLayout的collectionView的numberOfItemInSection這個方法得到一組中的全部cell代理
// 得到一組中的全部cell NSInteger cellCount = [self.collectionView numberOfItemsInSection:0];
經過for循環(循環次數爲一組中有多少個cell)建立佈局屬性,在循環中須要計算每個cell的frame 因此後臺要給真實的圖片尺寸(若是不給,本身計算的尺寸會形成圖片閃)code
同時顯示時通常都會等比例縮放。orm
CGFloat contentWidth = self.collectionView.bounds.size.width - self.sectionInset.left - self.sectionInset.right; CGFloat cellW = (contentWidth - (self.columnCount - 1) * self.minimumInteritemSpacing) / self.columnCount;
當要得到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];
在計算cellX的時候,若是經過 NSInteger col = i % self.columnCount;獲取列號
這樣每次都是按順序排列圖片的,那麼若是圖片的尺寸良莠不齊有的特別短有的又特別長,不巧的是長的都在一列短的又都在一列這樣會形成美觀性會不好,那麼怎麼解決這個問題呢,咱們能不能讓每一行添加完畢後,下一行在添加的時候將第一個添加在上一行高度最短的那面圖片下面呢?答案固然是能夠的-_-!
這時候咱們就要定義一個可變字典屬性,來存儲每一列的高度,用列號來當字典的key
// 用來記錄每一列的最的高度 @property (nonatomic, strong) NSMutableDictionary *colDict;
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];
CGFloat cellX = self.sectionInset.left + (cellW + self.minimumInteritemSpacing) * col;
計算cellY
// 用列號當字典的key NSString *colStr = [NSString stringWithFormat:@"%ld", col]; CGFloat cellY = [self.colDict[colStr] floatValue]; // 累計每一列的高度 self.colDict[colStr] = @(cellY + cellH + self.minimumLineSpacing);
這樣咱們就計算完了每個cell的X,Y,W,H,咱們來設置佈局屬性的frame
// 設置屬性的frame attr.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];
// 用來取出最高那一列的列號 - (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; }
- (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; }
這樣咱們簡單的瀑布流框架就搭建成功了~