前言
最近項目中使用了很多UICollectionView蚜迅,發(fā)現(xiàn)對其了解真是太少了,UICollectionViewLayout的自定義布局真的可以實現(xiàn)很多效果秉宿,趁這次機會好好記錄一下知識點荆烈,以前雖然用過,但沒有系統(tǒng)的整理總結(jié)過豹障。這兩天我為UICollectionView做一個整理。包括基本使用焦匈,自定義布局血公,動畫這塊我會的不多后面有機會在再記下來。
UICollectionView的構(gòu)成部分
Cells
Supplementary Views 追加視圖 (類似Header或者Footer)
Decoration Views 裝飾視圖 (用作背景展示)
而在表面下缓熟,由兩個方面對UICollectionView進行支持累魔。其中之一和tableView一樣摔笤,即提供數(shù)據(jù)的UICollectionViewDataSource以及處理用戶交互的UICollectionViewDelegate和布局方面的UICollectionViewDelegateFlowLayout。另一方面垦写,對于cell的樣式和組織方式吕世,由于collectionView比tableView要復(fù)雜得多,因此沒有按照類似于tableView的style的方式來定義梯投,而是專門使用了一個類來對collectionView的布局和行為進行描述命辖,這就是UICollectionViewLayout。
UICollectionView的創(chuàng)建
UICollectionView的創(chuàng)建方式和UITableview相似都是init方法分蓖,不同的是UICollectionView初始化時需要傳入一個UICollectionViewLayout對象用于對其Item進行布局尔艇,這個UICollectionViewLayout對象可以使用系統(tǒng)的UICollectionViewFlowLayout,也可以自定義么鹤,如果想要實現(xiàn)一些炫酷的動畫或者特定的樣式(例如瀑布流)都需要去自定義這個對象终娃。下面是創(chuàng)建UICollectionView的代碼。
-(DWCollectionView*)dwCollectionView{
if (!_dwCollectionView) {
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.itemSize = CGSizeMake(100, 30);
layout.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10);
_dwCollectionView = [[DWCollectionView alloc] initWithFrame:CGRectMake(0, 64, self.view.bounds.size.width, 400) collectionViewLayout:layout];
[_dwCollectionView registerClass:[DWCollectionViewCell class] forCellWithReuseIdentifier:@"UICollectionViewCellID"];
_dwCollectionView.delegate = self;
_dwCollectionView.dataSource = self;
_dwCollectionView.backgroundColor = [UIColor yellowColor];
}
return _dwCollectionView;
}
UICollectionView的常用屬性和代理方法
UICollectionView有三個代理蒸甜,除去和UITableview一樣的delegate和dataSource尝抖,還有一個ios
10新增的prefetchDataSource,三個代理中前兩個delegate用于處理用戶交互迅皇,dataSource用于提供數(shù)據(jù)源。
dataSource代理方法
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section;
// The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath:
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;
delegate常用代理方法
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
UICollectionViewDelegateFlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section;
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section;
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section;
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section;
屬性方法
- (void)registerClass:(nullable Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(nullable UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier;
///注冊一個headerView或footerView
- (void)registerClass:(nullable Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(nullable UINib *)nib forSupplementaryViewOfKind:(NSString *)kind withReuseIdentifier:(NSString *)identifier;
///獲取指定IndexPath的Cell和Supplementary Views
- (__kindof UICollectionViewCell *)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath;
- (__kindof UICollectionReusableView *)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath;
其他還有很多屬性和tableview相似就不多說了
UICollectionView的布局
UICollectionView的布局可以分為三種方式:
1.初始化時傳入的UICollectionViewLayout對象衙熔,通過設(shè)置UICollectionViewLayout對象屬性的值可以設(shè)置item的基本布局登颓,包括大小,間距等红氯。
2.也可以實現(xiàn)UICollectionViewLayoutDelegate協(xié)議對應(yīng)的方法框咙,返回布局需要的值。
3.自定義一個UICollectionViewLayout對象重寫對應(yīng)方法返回自定義的布局痢甘。
注意:同時設(shè)置1和2喇嘱,2的優(yōu)先級更高。
UICollectionViewFlowLayout常用屬性
//item的最小行間距
@property (nonatomic) CGFloat minimumLineSpacing;
//item之間的最小間距塞栅,這個數(shù)據(jù)設(shè)置的是最小的間距者铜,當間距小于這個值時,item就會換行顯示放椰,但是如果你設(shè)置的是10作烟,實際間距是20也是不會換行的只有小于這個值時才會換行。
@property (nonatomic) CGFloat minimumInteritemSpacing;
///item的大小
@property (nonatomic) CGSize itemSize;
//此屬性8.0以后有效砾医,作用:類似一個占位符拿撩,當加載item時會先加載這個size,顯示的時候 根據(jù) 子控件的autolayout 的約束算出自適應(yīng)內(nèi)容的 size如蚜;
/**
*1.設(shè)置estimatedItemSize的值压恒,隨便給也行
*2.對子控件進行約束
*3.如需進一步操作size可以在cell中重寫preferredLayoutAttributesFittingAttributes方法
*/
@property (nonatomic) CGSize estimatedItemSize NS_AVAILABLE_IOS(8_0); // defaults to CGSizeZero - setting a non-zero size enables cells that self-size via -preferredLayoutAttributesFittingAttributes:
//設(shè)置滑動方向
@property (nonatomic) UICollectionViewScrollDirection scrollDirection; // default is UICollectionViewScrollDirectionVertical
//區(qū)頭的size影暴,設(shè)置寬度值無效
@property (nonatomic) CGSize headerReferenceSize;
//區(qū)尾的size,設(shè)置寬度值無效
@property (nonatomic) CGSize footerReferenceSize;
//section之間的上左下右的間距(假設(shè)有三個區(qū)豎向滑動探赫,則top代表每個區(qū)第一行的item距離這個區(qū)上邊的距離型宙,boom代表這個區(qū)的最后一行item距這個區(qū)下邊的距離)不是item之間的上下左右的間距。
@property (nonatomic) UIEdgeInsets sectionInset;
estimatedItemSize屬性的簡單使用
只需設(shè)置estimatedItemSize替代itemSize即可實現(xiàn)簡單的布局,如果自適應(yīng)的label的長度會超出collectionview的寬度那需要label設(shè)置最大的寬度期吓,否則會出現(xiàn)布局錯誤早歇。
@implementation EstimatedItemCollectionViewCell
-(instancetype)initWithFrame:(CGRect)frame{
if (self =[super initWithFrame:frame]) {
///設(shè)置contentView上下左右約束為0
[self.contentView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.top.bottom.equalTo(self).with.offset(0);
make.width.mas_equalTo([UIScreen mainScreen].bounds.size.width-20);
}];
UIImageView *topImageView = [[UIImageView alloc] init];
topImageView.backgroundColor = [UIColor blueColor];
[self.contentView addSubview:topImageView];
[topImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.top.right.bottom.equalTo(self.contentView).with.offset(0);
// make.right.equalTo(self.contentView.mas_right).with.offset(-250);
// make.height.mas_equalTo(200);
}];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
label.backgroundColor = [UIColor orangeColor];
label.textAlignment = NSTextAlignmentCenter;
label.numberOfLines = 0;
// [label sizeToFit];
[self.contentView addSubview:label];
self.label = label;
//2.設(shè)置好subviews的約束
[label mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.bottom.equalTo(self.contentView).with.offset(0);
// make.top.equalTo(topImageView.mas_bottom).with.offset(0);
make.width.mas_lessThanOrEqualTo([UIScreen mainScreen].bounds.size.width-20);
// make.width.mas_greaterThanOrEqualTo(@(100));
}];
}
return self;
}
///3.如果不需要更多關(guān)于UICollectionViewLayoutAttributes的操作,只是向下面這樣重新賦值給size建議不要重寫
//preferredLayoutAttributesFittingAttributes: 方法默認調(diào)整Size屬性來適應(yīng) self-sizing Cell讨勤,所以重寫的時候需要先調(diào)用父類方法箭跳,再在返回的 UICollectionViewLayoutAttributes 對象上做你想要做的修改。
- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
///此處調(diào)用父類所等到的attributes 和你最后得到的layoutAttributes的size完全一樣潭千,如果只是修改size完全沒必要重寫,如果layout沒有設(shè)置layout.estimatedItemSize谱姓,則attributes和layoutAttributes的size一樣
UICollectionViewLayoutAttributes *attributes = [super preferredLayoutAttributesFittingAttributes:layoutAttributes];
NSLog(@"%f====%f",layoutAttributes.size.width,layoutAttributes.size.height);
NSLog(@"%f********%f",attributes.size.width,attributes.size.height);
///父類已經(jīng)調(diào)用
CGSize size = [self.contentView systemLayoutSizeFittingSize:layoutAttributes.size];
CGRect newFrame = layoutAttributes.frame;
newFrame.size.width = size.width;
layoutAttributes.frame = newFrame;
NSLog(@"%f------%f",size.width,size.height);
return attributes;
}
自定義UICollectionViewFlowLayout
先看一張圖,下面這張圖是使用estimatedItemSize來布局的一個collcetionview刨晴,現(xiàn)在我不想讓第一行兩個item之間的空隙太大屉来,這時候使用系統(tǒng)的layout向縮小是不可行的,因為就算我們設(shè)置minimumInteritemSpacing為10狈癞,上面已經(jīng)說了這個屬性只是item之間的最小間隔茄靠,item之間的實際間隔是可以大于10的,這時候如果用系統(tǒng)的layout就不是很好實現(xiàn)蝶桶,如果我們通過自定義layout則是很好實現(xiàn)的慨绳。
這張圖就可以實現(xiàn)我們上面的需求了,這就是通過自定義layout來實現(xiàn)的布局
自定義layout
自定義一個layout實際上很簡單真竖,你只需要創(chuàng)建一個繼承與UICollectionViewLayout的子類即可脐雪,然后重寫一部分方法,在這些方法里去實現(xiàn)你想要的布局方式恢共。
常用的方法
每次layout更新期間collectionview都會首先調(diào)用這個方法战秋,為將要開始的更新做準備,可以在此準備要使用的讨韭,你想要的layout的布局數(shù)組脂信。
///每次更新layout布局都會首先調(diào)用此方法
-(void)prepareLayout{
NSLog(@"---------1");
///和init相似,必須call super的prepareLayout以保證初始化正確
[super prepareLayout];
///1.首先被調(diào)用
[self.attributesArray removeAllObjects];
NSInteger itemCount = [self.collectionView numberOfItemsInSection:0];
for (int i =0; i<itemCount; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath];
[self.attributesArray addObject:attributes];
if (i==self.widthArray.count-1) {
[self loadOldAttributes:attributes.frame];
}
}
}
collectionViewContentSize方法返回collectionview的ContentSize透硝,每次更新會調(diào)用兩次吉嚣,第一次是開始更新時調(diào)用,第二次是layoutAttributesForElementsInRect方法返回所有item的約束后調(diào)用蹬铺。
///返回collectionView的內(nèi)容的尺寸
-(CGSize)collectionViewContentSize{
///2.其次被調(diào)用(layoutAttributesForElementsInRect 調(diào)用后會在此調(diào)用此方法)
NSLog(@"---%f------2",self.maxY);
return CGSizeMake(self.collectionView.bounds.size.width, self.maxY);
}
返回rect中的所有的元素的布局屬性,返回的是包含UICollectionViewLayoutAttributes的NSArray,UICollectionViewLayoutAttributes可以是cell尝哆,追加視圖或裝飾視圖的信息,通過不同的UICollectionViewLayoutAttributes初始化方法可以得到不同類型的,初始的layout的外觀將由該方法返回的UICollectionViewLayoutAttributes來決定甜攀。
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
///3.被調(diào)用
NSLog(@"---------3");
return self.attributesArray;
}
返回對應(yīng)于indexPath的位置的cell的布局屬性,返回指定indexPath的item的布局信息秋泄。子類必須重載該方法,該方法只能為cell提供布局信息琐馆,不能為補充視圖和裝飾視圖提供。
///返回對應(yīng)于indexPath的位置的cell的布局屬性,返回指定indexPath的item的布局信息恒序。子類必須重載該方法,該方法只能為cell提供布局信息瘦麸,不能為補充視圖和裝飾視圖提供。
-(UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:(NSIndexPath*)indexPath{
UICollectionViewLayoutAttributes *attributs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
NSNumber *currentWidthNumber = self.widthArray[indexPath.row];
CGFloat width = currentWidthNumber.floatValue;
///沒有換行所以超出部分不顯示(不寫下面的代碼也不會報錯歧胁,不知道為啥)
if (width>[UIScreen mainScreen].bounds.size.width-(self.left+self.right)) {
width = [UIScreen mainScreen].bounds.size.width - (self.left+self.right);
}
CGFloat height = 30;
CGRect currentFrame = CGRectZero;
if (1) {
if (self.attributesArray.count!=0) {
///1.取出上一個item的attributes
UICollectionViewLayoutAttributes *lastAttributs = [self.attributesArray lastObject];
CGRect lastFrame = lastAttributs.frame;
///判斷當前item和上一個item是否在同一個row
if (CGRectGetMaxX(lastAttributs.frame)+self.right==self.collectionView.bounds.size.width) {
///不在同一row
currentFrame.origin.x = self.left;
currentFrame.origin.y = CGRectGetMaxY(lastFrame) +self.top;
currentFrame.size.width = width;
currentFrame.size.height = height;
attributs.frame = currentFrame;
}else{
///上一個item的最大x值+當前item的寬度和左邊距
CGFloat totleWidth = CGRectGetMaxX(lastFrame)+(self.between+width+self.right);
///判斷上一個item所在row的剩余寬度是否還夠顯示當前item
if (totleWidth>=self.collectionView.bounds.size.width) {
///不足以顯示當前item的寬度
///將和上一個item在同一個row的item的放在同一個數(shù)組
NSMutableArray *sameYArray = [NSMutableArray array];
for (UICollectionViewLayoutAttributes *subAttributs in self.attributesArray) {
if (subAttributs.frame.origin.y==lastFrame.origin.y) {
[sameYArray addObject:subAttributs];
}
}
///判斷出上一row還剩下多少寬度
CGFloat sameYWidth = 0.0;
for (UICollectionViewLayoutAttributes *sameYAttributs in sameYArray) {
sameYWidth += sameYAttributs.size.width;
}
sameYWidth = sameYWidth + (self.left+self.right+(sameYArray.count-1)*self.between);
///上一個row所剩下的寬度
CGFloat sameYBetween = (self.collectionView.bounds.size.width-sameYWidth)/sameYArray.count;
for (UICollectionViewLayoutAttributes *sameYAttributs in sameYArray) {
CGFloat sameAttributeWidth = sameYAttributs.size.width;
CGFloat sameAttributeHeight = sameYAttributs.size.height;
CGRect sameYAttributsFrame = sameYAttributs.frame;
///更新sameYAttributs寬度使之均衡顯示
sameAttributeWidth += sameYBetween;
sameYAttributs.size = CGSizeMake(sameAttributeWidth, sameAttributeHeight);
NSInteger index = [sameYArray indexOfObject:sameYAttributs];
sameYAttributsFrame.origin.x += (sameYBetween*index);
sameYAttributsFrame.size.width = sameAttributeWidth;
sameYAttributs.frame = sameYAttributsFrame;
}
currentFrame.origin.x = self.left;
currentFrame.origin.y = CGRectGetMaxY(lastFrame)+self.top;
currentFrame.size.width = width;
currentFrame.size.height = height;
attributs.frame = currentFrame;
}else{
currentFrame.origin.x = CGRectGetMaxX(lastFrame)+self.between;
currentFrame.origin.y = lastFrame.origin.y;
currentFrame.size.width = width;
currentFrame.size.height = height;
attributs.frame = currentFrame;
}
}
}else{
currentFrame.origin.x = self.left;
currentFrame.origin.y = self.top;
currentFrame.size.width = width;
currentFrame.size.height = height;
attributs.frame = currentFrame;
}
}
// attributs.size = CGSizeMake(width, 30);
self.maxY = CGRectGetMaxY(attributs.frame)+10;
NSLog(@"%f===%f===%f===%f",attributs.frame.origin.x,attributs.frame.origin.y,attributs.frame.size.width,attributs.frame.size.height);
return attributs;
}
///返回對應(yīng)于indexPath的位置的追加視圖的布局屬性滋饲,如果沒有追加視圖可不重載
-(UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath{
return [super layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath];
}
///返回對應(yīng)于indexPath的位置的裝飾視圖的布局屬性,如果沒有裝飾視圖可不重載
-(UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString*)decorationViewKind atIndexPath:(NSIndexPath*)indexPath{
return [super layoutAttributesForDecorationViewOfKind:decorationViewKind atIndexPath:indexPath];
}
///當邊界發(fā)生改變時喊巍,是否應(yīng)該刷新布局屠缭。如果YES則在邊界變化(一般是scroll到其他地方)時,將重新計算需要的布局信息崭参。
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
return [super shouldInvalidateLayoutForBoundsChange:newBounds];
}
注意點: 另外呵曹,在需要更新layout時,需要給當前l(fā)ayout發(fā)送 -invalidateLayout何暮,該消息會立即返回奄喂,并且預(yù)約在下一個loop的時候刷新當前l(fā)ayout,這一點和UIView的setNeedsLayout方法十分類似海洼。在-invalidateLayout后的下一個collectionView的刷新loop中跨新,又會從prepareLayout開始,依次再調(diào)用-collectionViewContentSize和-layoutAttributesForElementsInRect來生成更新后的布局坏逢。
全部代碼
#import "DWCollectionViewLayout.h"
#define DWScreenH = [UIScreen mainScreen].bounds.size.height
#define DWScreenW = [UIScreen mainScreen].bounds.size.width
@interface DWCollectionViewLayout ()
@property (nonatomic,strong) NSMutableArray *attributesArray;
@property (nonatomic,assign) CGFloat maxY;
@property (nonatomic,assign) CGFloat left;
@property (nonatomic,assign) CGFloat right;
@property (nonatomic,assign) CGFloat top;
@property (nonatomic,assign) CGFloat between;
@end
@implementation DWCollectionViewLayout
-(instancetype)initWithArray:(NSMutableArray*)widthArray edgeInsets:(UIEdgeInsets)insets{
if (self = [super init]) {
self.widthArray = widthArray;
NSLog(@"==***==%p",self.widthArray);
self.left = insets.left;
self.right = insets.right;
self.top = insets.top;
self.between = insets.bottom;
}
return self;
}
/**
*另外需要了解的是域帐,在初始化一個UICollectionViewLayout實例后,會有一系列準備方法被自動調(diào)用词疼,以保證layout實例的正確。
*首先帘腹,將被調(diào)用贰盗,默認下該方法什么沒做,但是在自己的子類實現(xiàn)中阳欲,一般在該方法中設(shè)定一些必要的layout的結(jié)構(gòu)和初始需要的參數(shù)等舵盈。
*/
-(void)prepareLayout{
NSLog(@"---------1");
///和init相似,必須call super的prepareLayout以保證初始化正確
[super prepareLayout];
///1.首先被調(diào)用
[self.attributesArray removeAllObjects];
NSInteger itemCount = [self.collectionView numberOfItemsInSection:0];
for (int i =0; i<itemCount; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath];
[self.attributesArray addObject:attributes];
if (i==self.widthArray.count-1) {
[self loadOldAttributes:attributes.frame];
}
}
}
///返回collectionView的內(nèi)容的尺寸
-(CGSize)collectionViewContentSize{
///2.其次被調(diào)用(layoutAttributesForElementsInRect 調(diào)用后會在此調(diào)用此方法)
NSLog(@"---%f------2",self.maxY);
return CGSizeMake(self.collectionView.bounds.size.width, self.maxY);
}
///返回rect中的所有的元素的布局屬性,返回的是包含UICollectionViewLayoutAttributes的NSArray
///UICollectionViewLayoutAttributes可以是cell球化,追加視圖或裝飾視圖的信息秽晚,通過不同的UICollectionViewLayoutAttributes初始化方法可以得到不同類型的
///初始的layout的外觀將由該方法返回的UICollectionViewLayoutAttributes來決定。
///rect 為collectionview 的rect筒愚,(高度超出當前屏幕的高度后赴蝇,rect的height會翻倍)
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
///3.被調(diào)用
NSLog(@"---------3");
NSArray *array = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes *attributes in array) {
if (attributes.representedElementCategory == UICollectionElementCategoryCell) {
}
}
return self.attributesArray;
}
///返回對應(yīng)于indexPath的位置的cell的布局屬性,返回指定indexPath的item的布局信息。子類必須重載該方法,該方法只能為cell提供布局信息巢掺,不能為補充視圖和裝飾視圖提供句伶。
-(UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:(NSIndexPath*)indexPath{
UICollectionViewLayoutAttributes *attributs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
NSNumber *currentWidthNumber = self.widthArray[indexPath.row];
CGFloat width = currentWidthNumber.floatValue;
///沒有換行所以超出部分不顯示(不寫下面的代碼也不會報錯劲蜻,不知道為啥)
if (width>[UIScreen mainScreen].bounds.size.width-(self.left+self.right)) {
width = [UIScreen mainScreen].bounds.size.width - (self.left+self.right);
}
CGFloat height = 30;
CGRect currentFrame = CGRectZero;
if (1) {
if (self.attributesArray.count!=0) {
///1.取出上一個item的attributes
UICollectionViewLayoutAttributes *lastAttributs = [self.attributesArray lastObject];
CGRect lastFrame = lastAttributs.frame;
///判斷當前item和上一個item是否在同一個row
if (CGRectGetMaxX(lastAttributs.frame)+self.right==self.collectionView.bounds.size.width) {
///不在同一row
currentFrame.origin.x = self.left;
currentFrame.origin.y = CGRectGetMaxY(lastFrame) +self.top;
currentFrame.size.width = width;
currentFrame.size.height = height;
attributs.frame = currentFrame;
}else{
///上一個item的最大x值+當前item的寬度和左邊距
CGFloat totleWidth = CGRectGetMaxX(lastFrame)+(self.between+width+self.right);
///判斷上一個item所在row的剩余寬度是否還夠顯示當前item
if (totleWidth>=self.collectionView.bounds.size.width) {
///不足以顯示當前item的寬度
///將和上一個item在同一個row的item的放在同一個數(shù)組
NSMutableArray *sameYArray = [NSMutableArray array];
for (UICollectionViewLayoutAttributes *subAttributs in self.attributesArray) {
if (subAttributs.frame.origin.y==lastFrame.origin.y) {
[sameYArray addObject:subAttributs];
}
}
///判斷出上一row還剩下多少寬度
CGFloat sameYWidth = 0.0;
for (UICollectionViewLayoutAttributes *sameYAttributs in sameYArray) {
sameYWidth += sameYAttributs.size.width;
}
sameYWidth = sameYWidth + (self.left+self.right+(sameYArray.count-1)*self.between);
///上一個row所剩下的寬度
CGFloat sameYBetween = (self.collectionView.bounds.size.width-sameYWidth)/sameYArray.count;
for (UICollectionViewLayoutAttributes *sameYAttributs in sameYArray) {
CGFloat sameAttributeWidth = sameYAttributs.size.width;
CGFloat sameAttributeHeight = sameYAttributs.size.height;
CGRect sameYAttributsFrame = sameYAttributs.frame;
///更新sameYAttributs寬度使之均衡顯示
sameAttributeWidth += sameYBetween;
sameYAttributs.size = CGSizeMake(sameAttributeWidth, sameAttributeHeight);
NSInteger index = [sameYArray indexOfObject:sameYAttributs];
sameYAttributsFrame.origin.x += (sameYBetween*index);
sameYAttributsFrame.size.width = sameAttributeWidth;
sameYAttributs.frame = sameYAttributsFrame;
}
currentFrame.origin.x = self.left;
currentFrame.origin.y = CGRectGetMaxY(lastFrame)+self.top;
currentFrame.size.width = width;
currentFrame.size.height = height;
attributs.frame = currentFrame;
}else{
currentFrame.origin.x = CGRectGetMaxX(lastFrame)+self.between;
currentFrame.origin.y = lastFrame.origin.y;
currentFrame.size.width = width;
currentFrame.size.height = height;
attributs.frame = currentFrame;
}
}
}else{
currentFrame.origin.x = self.left;
currentFrame.origin.y = self.top;
currentFrame.size.width = width;
currentFrame.size.height = height;
attributs.frame = currentFrame;
}
}
// attributs.size = CGSizeMake(width, 30);
self.maxY = CGRectGetMaxY(attributs.frame)+10;
NSLog(@"%f===%f===%f===%f",attributs.frame.origin.x,attributs.frame.origin.y,attributs.frame.size.width,attributs.frame.size.height);
return attributs;
}
///返回對應(yīng)于indexPath的位置的追加視圖的布局屬性,如果沒有追加視圖可不重載
-(UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath{
return [super layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath];
}
///返回對應(yīng)于indexPath的位置的裝飾視圖的布局屬性考余,如果沒有裝飾視圖可不重載
-(UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString*)decorationViewKind atIndexPath:(NSIndexPath*)indexPath{
return [super layoutAttributesForDecorationViewOfKind:decorationViewKind atIndexPath:indexPath];
}
///當邊界發(fā)生改變時先嬉,是否應(yīng)該刷新布局。如果YES則在邊界變化(一般是scroll到其他地方)時楚堤,將重新計算需要的布局信息疫蔓。
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
return [super shouldInvalidateLayoutForBoundsChange:newBounds];
}
/**
另外,在需要更新layout時身冬,需要給當前l(fā)ayout發(fā)送 -invalidateLayout衅胀,該消息會立即返回,并且預(yù)約在下一個loop的時候刷新當前l(fā)ayout吏恭,這一點和UIView的setNeedsLayout方法十分類似拗小。在-invalidateLayout后的下一個collectionView的刷新loop中,又會從prepareLayout開始樱哼,依次再調(diào)用-collectionViewContentSize和-layoutAttributesForElementsInRect來生成更新后的布局哀九。
*/
-(void)loadOldAttributes:(CGRect)lastFrame{
///將和上一個item在同一個row的item的放在同一個數(shù)組
NSMutableArray *sameYArray = [NSMutableArray array];
for (UICollectionViewLayoutAttributes *subAttributs in self.attributesArray) {
if (subAttributs.frame.origin.y==lastFrame.origin.y) {
[sameYArray addObject:subAttributs];
}
}
///判斷出上一row還剩下多少寬度
CGFloat sameYWidth = 0.0;
for (UICollectionViewLayoutAttributes *sameYAttributs in sameYArray) {
sameYWidth += sameYAttributs.size.width;
}
sameYWidth = sameYWidth + (self.left+self.right+(sameYArray.count-1)*self.between);
///上一個row所剩下的寬度
CGFloat sameYBetween = (self.collectionView.bounds.size.width-sameYWidth)/sameYArray.count;
for (UICollectionViewLayoutAttributes *sameYAttributs in sameYArray) {
CGFloat sameAttributeWidth = sameYAttributs.size.width;
CGFloat sameAttributeHeight = sameYAttributs.size.height;
CGRect sameYAttributsFrame = sameYAttributs.frame;
///更新sameYAttributs寬度使之均衡顯示
sameAttributeWidth += sameYBetween;
sameYAttributs.size = CGSizeMake(sameAttributeWidth, sameAttributeHeight);
NSInteger index = [sameYArray indexOfObject:sameYAttributs];
sameYAttributsFrame.origin.x += (sameYBetween*index);
sameYAttributsFrame.size.width = sameAttributeWidth;
sameYAttributs.frame = sameYAttributsFrame;
}
}
-(NSMutableArray*)attributesArray{
if (!_attributesArray) {
_attributesArray = [NSMutableArray array];
}
return _attributesArray;
}
@end
區(qū)頭懸浮
#import "DWReusableLayout.h"
@interface DWReusableLayout ()
@property (nonatomic,assign) CGFloat naviHeight;
@end
@implementation DWReusableLayout
-(instancetype)init
{
self = [super init];
if (self)
{
self.naviHeight = 0.0;
}
return self;
}
/*
// 作用:返回指定區(qū)域的cell布局對象
// 什么時候調(diào)用:指定新的區(qū)域的時候調(diào)用
(<__kindof UICollectionViewLayoutAttributes *> iOS9之后的泛型 )
- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
*/
/** iOS9 之前的寫法 作用第24行代碼有寫*/
//UICollectionViewLayoutAttributes:我稱它為collectionView中的item(包括cell和header、footer這些)的《結(jié)構(gòu)信息》
- (NSArray *) layoutAttributesForElementsInRect:(CGRect)rect
{
//截取到父類所返回的數(shù)組(里面放的是當前屏幕所能展示的item的結(jié)構(gòu)信息)搅幅,并轉(zhuǎn)化成不可變數(shù)組
NSMutableArray *superArray = [[super layoutAttributesForElementsInRect:rect] mutableCopy];
//創(chuàng)建存索引的數(shù)組阅束,無符號(正整數(shù)),無序(不能通過下標取值)茄唐,不可重復(fù)(重復(fù)的話會自動過濾)
NSMutableIndexSet *noneHeaderSections = [NSMutableIndexSet indexSet];
//遍歷superArray息裸,得到一個當前屏幕中所有的section數(shù)組
for (UICollectionViewLayoutAttributes *attributes in superArray)
{
//如果當前的元素分類是一個cell,將cell所在的分區(qū)section加入數(shù)組沪编,重復(fù)的話會自動過濾
if (attributes.representedElementCategory == UICollectionElementCategoryCell)
{
[noneHeaderSections addIndex:attributes.indexPath.section];
}
}
//遍歷superArray呼盆,將當前屏幕中擁有的header的section從數(shù)組中移除,得到一個當前屏幕中沒有header的section數(shù)組
//正常情況下蚁廓,隨著手指往上移访圃,header脫離屏幕會被系統(tǒng)回收而cell尚在,也會觸發(fā)該方法
for (UICollectionViewLayoutAttributes *attributes in superArray)
{
//如果當前的元素是一個header相嵌,將header所在的section從數(shù)組中移除
if ([attributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader])
{
[noneHeaderSections removeIndex:attributes.indexPath.section];
}
}
//遍歷當前屏幕中沒有header的section數(shù)組
[noneHeaderSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
//取到當前section中第一個item的indexPath
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:idx];
//獲取當前section在正常情況下已經(jīng)離開屏幕的header結(jié)構(gòu)信息
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];
//如果當前分區(qū)確實有因為離開屏幕而被系統(tǒng)回收的header
if (attributes)
{
//將該header結(jié)構(gòu)信息重新加入到superArray中去
[superArray addObject:attributes];
}
}];
//遍歷superArray腿时,改變header結(jié)構(gòu)信息中的參數(shù),使它可以在當前section還沒完全離開屏幕的時候一直顯示
for (UICollectionViewLayoutAttributes *attributes in superArray) {
//如果當前item是header
if ([attributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader])
{
//得到當前header所在分區(qū)的cell的數(shù)量
NSInteger numberOfItemsInSection = [self.collectionView numberOfItemsInSection:attributes.indexPath.section];
//得到第一個item的indexPath
NSIndexPath *firstItemIndexPath = [NSIndexPath indexPathForItem:0 inSection:attributes.indexPath.section];
//得到最后一個item的indexPath
NSIndexPath *lastItemIndexPath = [NSIndexPath indexPathForItem:MAX(0, numberOfItemsInSection-1) inSection:attributes.indexPath.section];
//得到第一個item和最后一個item的結(jié)構(gòu)信息
UICollectionViewLayoutAttributes *firstItemAttributes, *lastItemAttributes;
if (numberOfItemsInSection>0)
{
//cell有值饭宾,則獲取第一個cell和最后一個cell的結(jié)構(gòu)信息
firstItemAttributes = [self layoutAttributesForItemAtIndexPath:firstItemIndexPath];
lastItemAttributes = [self layoutAttributesForItemAtIndexPath:lastItemIndexPath];
}else
{
//cell沒值,就新建一個UICollectionViewLayoutAttributes
firstItemAttributes = [UICollectionViewLayoutAttributes new];
//然后模擬出在當前分區(qū)中的唯一一個cell批糟,cell在header的下面,高度為0看铆,還與header隔著可能存在的sectionInset的top
CGFloat y = CGRectGetMaxY(attributes.frame)+self.sectionInset.top;
firstItemAttributes.frame = CGRectMake(0, y, 0, 0);
//因為只有一個cell徽鼎,所以最后一個cell等于第一個cell
lastItemAttributes = firstItemAttributes;
}
//獲取當前header的frame
CGRect rect = attributes.frame;
//當前的滑動距離 + 因為導(dǎo)航欄產(chǎn)生的偏移量,默認為64(如果app需求不同,需自己設(shè)置)
CGFloat offset = self.collectionView.contentOffset.y + _naviHeight;
//第一個cell的y值 - 當前header的高度 - 可能存在的sectionInset的top
///firstItemAttributes.frame.origin.y-self.sectionInset.top = CGRectGetMaxY(attributes)(即緊貼header的最大Y值 = header.frame.origin.y+header.bounds.size.height)
///firstItemAttributes.frame.origin.y-self.sectionInset.top = header.frame.origin.y+header.bounds.size.height
///header.frame.origin.y = firstItemAttributes.frame.origin.y-self.sectionInset.top-header.bounds.size.height
CGFloat headerY = firstItemAttributes.frame.origin.y - rect.size.height - self.sectionInset.top;
//哪個大取哪個纬傲,保證header懸停
//針對當前header基本上都是offset更加大满败,針對下一個header則會是headerY大,各自處理
CGFloat maxY = MAX(offset,headerY);
//最后一個cell的y值 + 最后一個cell的高度 + 可能存在的sectionInset的bottom - 當前header的高度
//當當前section的footer或者下一個section的header接觸到當前header的底部叹括,計算出的headerMissingY即為有效值
CGFloat headerMissingY = CGRectGetMaxY(lastItemAttributes.frame) + self.sectionInset.bottom - rect.size.height;
//給rect的y賦新值算墨,因為在最后消失的臨界點要跟誰消失,所以取小
///兩個區(qū)頭沒有接觸之前汁雷,offset<headerMissingY净嘀,所以rect.origin.y==偏移量,接觸時offset=headerMissingY侠讯,接觸后挖藏,offset>headerMissingY
///所以接觸后最小值就是headerMissingY(即上一個區(qū)的最大Y值-rect.size.height)
rect.origin.y = MIN(maxY,headerMissingY);
NSLog(@"%f-----%f----%f---%f",offset,headerY,headerMissingY,rect.origin.y);
//給header的結(jié)構(gòu)信息的frame重新賦值
attributes.frame = rect;
//如果按照正常情況下,header離開屏幕被系統(tǒng)回收,而header的層次關(guān)系又與cell相等厢漩,如果不去理會膜眠,會出現(xiàn)cell在header上面的情況
//通過打印可以知道cell的層次關(guān)系zIndex數(shù)值為0,我們可以將header的zIndex設(shè)置成1溜嗜,如果不放心宵膨,也可以將它設(shè)置成非常大,這里隨便填了個7
attributes.zIndex = 7;
}
}
//轉(zhuǎn)換回不可變數(shù)組炸宵,并返回
return [superArray copy];
}
//return YES;表示一旦滑動就實時調(diào)用上面這個layoutAttributesForElementsInRect:方法
- (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBound
{
return YES;
}
@end
瀑布流
#import "DWWateFallLayout.h"
@interface DWWateFallLayout ()
@property (nonatomic,strong) NSMutableArray *attributesArray;
@property (nonatomic,strong) NSArray<NSNumber*> *itemHeightArray;
@property (nonatomic,strong) NSMutableArray<UICollectionViewLayoutAttributes*> *itemArray;
@end
@implementation DWWateFallLayout
-(instancetype)initWithHeightArray:(NSArray*)heightArray{
if (self = [super init]) {
self.itemHeightArray = heightArray;
}
return self;
}
-(void)prepareLayout{
///和init相似辟躏,必須call super的prepareLayout以保證初始化正確
[super prepareLayout];
///1.首先被調(diào)用
[self.attributesArray removeAllObjects];
[self.itemArray removeAllObjects];
///獲取當前collectionView對應(yīng)區(qū)的item
NSInteger count = [self.collectionView numberOfItemsInSection:0];
for (int i =0; i<count; i++) {
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
[self.attributesArray addObject:attributes];
}
}
///返回collectionView的內(nèi)容的尺寸
-(CGSize)collectionViewContentSize{
///2.其次被調(diào)用(layoutAttributesForElementsInRect 調(diào)用后會在此調(diào)用此方法)
CGFloat maxContentHeight = CGRectGetMaxY([self.itemArray firstObject].frame);
for (UICollectionViewLayoutAttributes *attributes in self.itemArray) {
if (maxContentHeight<CGRectGetMaxY(attributes.frame)) {
maxContentHeight = CGRectGetMaxY(attributes.frame);
}
}
return CGSizeMake(self.collectionView.bounds.size.width, maxContentHeight);
}
///返回rect中的所有的元素的布局屬性,返回的是包含UICollectionViewLayoutAttributes的NSArray
///UICollectionViewLayoutAttributes可以是cell,追加視圖或裝飾視圖的信息土全,通過不同的UICollectionViewLayoutAttributes初始化方法可以得到不同類型的
///初始的layout的外觀將由該方法返回的UICollectionViewLayoutAttributes來決定捎琐。
///rect 為collectionview 的rect,(高度超出collectionview高度后裹匙,rect的height會翻倍)
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
///3.被調(diào)用
return self.attributesArray;
}
///返回對應(yīng)于indexPath的位置的cell的布局屬性,返回指定indexPath的item的布局信息瑞凑。子類必須重載該方法,該方法只能為cell提供布局信息,不能為補充視圖和裝飾視圖提供概页。
-(UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:(NSIndexPath*)indexPath{
UICollectionViewLayoutAttributes *attributs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
///item的寬度籽御,根據(jù)左右間距和中間間距算出item寬度
CGFloat itemWidth = (self.collectionView.bounds.size.width - (10+10+10+10))/3.0;
///item的高度
CGFloat itemHeight = self.itemHeightArray[indexPath.row].floatValue;
if (self.itemArray.count<3) {
[self.itemArray addObject:attributs];
CGRect itemFrame = CGRectMake(10+(itemWidth+10)*(self.itemArray.count-1), 10, itemWidth, itemHeight);
attributs.frame = itemFrame;
}else{
UICollectionViewLayoutAttributes *fristAttri = [self.itemArray firstObject];
CGFloat minY = CGRectGetMaxY(fristAttri.frame);
CGFloat Y = minY;
NSInteger index=0;
CGRect itemFrame = CGRectMake(fristAttri.frame.origin.x,CGRectGetMaxY(fristAttri.frame)+10, itemWidth, itemHeight);
for (UICollectionViewLayoutAttributes *attri in self.itemArray) {
if (minY>CGRectGetMaxY(attri.frame)) {
minY = CGRectGetMaxY(attri.frame);
Y = minY;
itemFrame = CGRectMake(attri.frame.origin.x,Y+10, itemWidth, itemHeight);
NSInteger currentIndex = [self.itemArray indexOfObject:attri];
index = currentIndex;
}
}
attributs.frame = itemFrame;
[self.itemArray replaceObjectAtIndex:index withObject:attributs];
}
return attributs;
}
-(NSMutableArray*)attributesArray{
if (!_attributesArray) {
_attributesArray = [NSMutableArray array];
}
return _attributesArray;
}
-(NSMutableArray*)itemArray{
if (!_itemArray) {
_itemArray = [NSMutableArray array];
}
return _itemArray;
}
@end