iOS-UICollectionView
1------------------------------------------------------------------------------------------------------------------------
本章通过先总体介绍UICollectionView及其常用方法,再结合一个实例,了解如何使用UICollectionView。
UICollectionView 和 UICollectionViewController 类是iOS6 新引进的API,用于展示集合视图,布局更加灵活,可实现多列布局,用法类似于UITableView 和 UITableViewController 类。
使用UICollectionView 必须实现UICollectionViewDataSource,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout这三个协议。
下面先给出常用到的一些方法。(只给出常用的,其他的可以查看相关API)
- #pragma mark -- UICollectionViewDataSource
- //定义展示的UICollectionViewCell的个数
- -(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
- {
- return 30;
- }
- //定义展示的Section的个数
- -(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
- {
- return 1;
- }
- //每个UICollectionView展示的内容
- -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
- {
- static NSString * CellIdentifier = @"GradientCell";
- UICollectionViewCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
- cell.backgroundColor = [UIColor colorWithRed:((10 * indexPath.row) / 255.0) green:((20 * indexPath.row)/255.0) blue:((30 * indexPath.row)/255.0) alpha:1.0f];
- return cell;
- }
- #pragma mark --UICollectionViewDelegateFlowLayout
- //定义每个UICollectionView 的大小
- - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
- {
- return CGSizeMake(96, 100);
- }
- //定义每个UICollectionView 的 margin
- -(UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
- {
- return UIEdgeInsetsMake(5, 5, 5, 5);
- }
- #pragma mark --UICollectionViewDelegate
- //UICollectionView被选中时调用的方法
- -(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
- {
- UICollectionViewCell * cell = (UICollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
- cell.backgroundColor = [UIColor whiteColor];
- }
- //返回这个UICollectionView是否可以被选择
- -(BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath
- {
- return YES;
- }
下面通过一个例子具体介绍下。(例子来自网络。但是是通过第三方获得的,无法取得链接。还望见谅。)
iOS CollectionView的出现是一大福利,再也不用用TableView来定义复杂的多栏表格了,用法与Table类似,只是Cell必须自己添加,无默认模式
由于CollectionView没有默认的Cell布局,所以一般还是自定义方便又快捷
一、自定义Cell
1、新建类CollectionCell继承自UICollectionViewCell
2、新建Xib,命名为CollectionCell.xib
a.选中CollectionCell.xib删掉默认的View,从控件中拖一个Collection View Cell(图3)到画布中,设置大小为95*116;
b.选中刚刚添加的Cell,更改类名为CollectionCell,如图4
c.在CollectionCell.xib的CollectionCell中添加一个ImageView和一个Label(图5)
d.创建映射, 图6,图7
e.选中CollectionCell.m , 重写init方法
- - (id)initWithFrame:(CGRect)frame
- {
- self = [super initWithFrame:frame];
- if (self)
- {
- // 初始化时加载collectionCell.xib文件
- NSArray *arrayOfViews = [[NSBundle mainBundle] loadNibNamed:@"CollectionCell" owner:self options:nil];
- // 如果路径不存在,return nil
- if (arrayOfViews.count < 1)
- {
- return nil;
- }
- // 如果xib中view不属于UICollectionViewCell类,return nil
- if (![[arrayOfViews objectAtIndex:0] isKindOfClass:[UICollectionViewCell class]])
- {
- return nil;
- }
- // 加载nib
- self = [arrayOfViews objectAtIndex:0];
- }
- return self;
- }
f.选中CollectionCell.xib 修改其identifier为CollectionCell。
二、定义UICollectionView;
1、拖动一个Collection View到指定ViewController的View上
2、连线dataSource和delegate,并创建映射,命名为CollectionView
3、选中CollectionView的标尺,将Cell Size的Width和Height改成与自定义的Cell一样的95*116,图8
4、选中CollectionView的属性,可以修改其属性,比如是垂直滑动,还是水平滑动,选择Vertical或Horizontal
5、选中CollectionViewCell,修改Class,继承自CollectionCell
5、在ViewDidLoad方法中声明Cell的类,在ViewDidLoad方法中添加,此句不声明,将无法加载,程序崩溃
其中,CollectionCell是这个Cell的标识(之前几步已经定义过了。 )
- [self.collectionView registerClass:[CollectionCell class] forCellWithReuseIdentifier:@"CollectionCell"];
6、在ViewController.h中声明代理
- @interface ViewController : UIViewController<UICollectionViewDataSource,UICollectionViewDelegate>
7、在.m文件中实现代理方法
- //每个section的item个数
- -(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
- {
- return 12;
- }
- -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
- {
- CollectionCell *cell = (CollectionCell *)[collectionView dequeueReusableCellWithReuseIdentifier:@"CollectionCell" forIndexPath:indexPath];
- //图片名称
- NSString *imageToLoad = [NSString stringWithFormat:@"%d.png", indexPath.row];
- //加载图片
- cell.imageView.image = [UIImage imageNamed:imageToLoad];
- //设置label文字
- cell.label.text = [NSString stringWithFormat:@"{%ld,%ld}",(long)indexPath.row,(long)indexPath.section];
- return cell;
- }
8 。效果如图10
点击某项后跳转事件与UITableView类似,实现代理方法
- -(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
UICollectionView基础
初始化部分:
UICollectionViewFlowLayout *flowLayout= [[UICollectionViewFlowLayout alloc]init]; self.myCollectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(20, 20, 250, 350) collectionViewLayout:flowLayout]; self.myCollectionView.backgroundColor = [UIColor grayColor]; [self.myCollectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@“myCell"]; self.myCollectionView.delegate = self; self.myCollectionView.dataSource = self; [self.view addSubview:self.myCollectionView];
UICollectionViewLayout
UICollectionViewLayout决定了UICollectionView如何显示在界面上,Apple提供了一个最简单的默认layout对象:UICollectionViewFlowLayout。
Flow Layout是一个Cells的线性布局方案,并具有页面和页脚。其可定制的内容如下:
itemSize属性
设定全局的Cell尺寸,如果想要单独定义某个Cell的尺寸,可以使用下面方法:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
minimumLineSpacing属性
设定全局的行间距,如果想要设定指定区内Cell的最小行距,可以使用下面方法:
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
minimumInteritemSpacing属性
设定全局的Cell间距,如果想要设定指定区内Cell的最小间距,可以使用下面方法:
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section;
scrollDirection属性
设定滚动方向,有UICollectionViewScrollDirectionVertical和UICollectionViewScrollDirectionHorizontal两个值。
headerReferenceSize属性与footerReferenceSize属性
设定页眉和页脚的全局尺寸,需要注意的是,根据滚动方向不同,header和footer的width和height中只有一个会起作用。如果要单独设置指定区内的页面和页脚尺寸,可以使用下面方法:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section
sectionInset属性
设定全局的区内边距,如果想要设定指定区的内边距,可以使用下面方法:
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;
然后需要实现三种类型的委托:UICollectionViewDataSource, UICollectionViewDelagate和UICollectionViewDelegateFlowLayout。
@interface ViewController : UIViewController <UICollectionViewDelegateFlowLayout, UICollectionViewDataSource>
因为UICollectionViewDelegateFlowLayout实际上是UICollectionViewDelegate的一个子协议,它继承了UICollectionViewDelegate,所以只需要在声明处写上UICollectionViewDelegateFlowLayout就行了。
UICollectionViewDataSource
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
返回collection view里区(section)的个数,如果没有实现该方法,将默认返回1:
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { return 2; }
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
返回指定区(section)包含的数据源条目数(number of items),该方法必须实现:
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return 7; }
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
返回某个indexPath对应的cell,该方法必须实现:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"myCell" forIndexPath:indexPath]; if(indexPath.section==0) { cell.backgroundColor = [UIColor redColor]; } else if(indexPath.section==1) { cell.backgroundColor = [UIColor greenColor]; } return cell; }
UICollectionViewCell结构上相对比较简单,由下至上:
- 首先是cell本身作为容器view
- 然后是一个大小自动适应整个cell的backgroundView,用作cell平时的背景
- 再其次是selectedBackgroundView,是cell被选中时的背景
- 最后是一个contentView,自定义内容应被加在这个view上
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString*)kind atIndexPath:(NSIndexPath *)indexPath
为collection view添加一个补充视图(页眉或页脚)
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
设定页眉的尺寸
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section
设定页脚的尺寸
- (void)registerClass:(Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString*)identifier
添加页眉和页脚以前需要注册类和标识:
添加补充视图的代码示例:
[self.myCollectionView registerClass:[MyHeadView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"hxwHeader"]; [self.myCollectionView registerClass:[MyHeadView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"hxwHeader"]; -(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { CGSize size = {240,25}; return size; } -(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section { CGSize size = {240,25}; return size; } - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { MyHeadView *headView; if([kind isEqual:UICollectionElementKindSectionHeader]) { headView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"hxwHeader" forIndexPath:indexPath]; [headView setLabelText:[NSString stringWithFormat:@"section %d's header",indexPath.section]]; } else if([kind isEqual:UICollectionElementKindSectionFooter]) { headView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"hxwHeader" forIndexPath:indexPath]; [headView setLabelText:[NSString stringWithFormat:@"section %d's footer",indexPath.section]]; } return headView; }
MyHeadView.h
#import <UIKit/UIKit.h> @interface MyHeadView : UICollectionReusableView - (void) setLabelText:(NSString *)text; @end
MyHeadView.m
#import "MyHeadView.h" @interface MyHeadView() @property (strong, nonatomic) UILabel *label; @end @implementation MyHeadView - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.label = [[UILabel alloc] init]; self.label.font = [UIFont systemFontOfSize:18]; [self addSubview:self.label]; } return self; } - (void) setLabelText:(NSString *)text { self.label.text = text; [self.label sizeToFit]; } @end
在注册Cell和补充视图时,也可以用新建xib文件的方式:
[self.myCollectionView registerNib:[UINib nibWithNibName:@"MyCollectionCell" bundle:nil] forCellWithReuseIdentifier:@"hxwCell"]; [self.myCollectionView registerNib:[UINib nibWithNibName:@"MySupplementaryView" bundle:nil] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"hxwHeader"]; [self.myCollectionView registerNib:[UINib nibWithNibName:@"MySupplementaryView" bundle:nil] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"hxwFooter"];
用这种方式注册后,甚至可以不用新建类去绑定这个xib,直接通过viewWithTag的方式获取xib里的控件:
UICollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind :kind withReuseIdentifier:@"hxwHeader" forIndexPath:indexPath]; UILabel *label = (UILabel *)[view viewWithTag:1]; label.text = @"empty";
UICollectionViewDelegateFlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
设定指定Cell的尺寸
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { if(indexPath.section==0 && indexPath.row==1) { return CGSizeMake(50, 50); } else { return CGSizeMake(75, 30); } }
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;
设定collectionView(指定区)的边距
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { if(section==0) { return UIEdgeInsetsMake(35, 25, 15, 25); } else { return UIEdgeInsetsMake(15, 15, 15, 15); } }
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
设定指定区内Cell的最小行距,也可以直接设置UICollectionViewFlowLayout的minimumLineSpacing属性
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { if(section==0) { return 10.0; } else { return 20.0; } }
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section;
设定指定区内Cell的最小间距,也可以直接设置UICollectionViewFlowLayout的minimumInteritemSpacing属性
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { if(section==0) { return 10.0; } else { return 20.0; } }
UICollectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
当指定indexPath处的item被选择时触发
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { [self.myArray removeObjectAtIndex:indexPath.row]; [collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]]; }
P.s. 当你删除或添加元素时,一定要更新numberOfItemsInSection的返回情况。
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
当指定indexPath处的item被取消选择时触发,仅在允许多选时被调用
下面是三个和高亮有关的方法:
- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath
- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath
- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath
事件的处理顺序如下:
- 手指按下
- shouldHighlightItemAtIndexPath (如果返回YES则向下执行,否则执行到这里为止)
- didHighlightItemAtIndexPath (高亮)
- 手指松开
- didUnhighlightItemAtIndexPath (取消高亮)
- shouldSelectItemAtIndexPath (如果返回YES则向下执行,否则执行到这里为止)
- didSelectItemAtIndexPath (执行选择事件)
如果只是简单实现点击后cell改变显示状态,只需要在cellForItemAtIndexPath方法里返回cell时,指定cell的selectedBackgroundView:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"myCell" forIndexPath:indexPath]; UIView* selectedBGView = [[UIView alloc] initWithFrame:cell.bounds]; selectedBGView.backgroundColor = [UIColor blueColor]; cell.selectedBackgroundView = selectedBGView; return cell; }
如果要实现点击时(手指未松开)的显示状态与点击后(手指松开)的显示状态,则需要通过上面提到的方法来实现:
- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath { return YES; } - (void)collectionView:(UICollectionView *)colView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewCell* cell = [colView cellForItemAtIndexPath:indexPath]; [cell setBackgroundColor:[UIColor purpleColor]]; } - (void)collectionView:(UICollectionView *)colView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewCell* cell = [colView cellForItemAtIndexPath:indexPath]; [cell setBackgroundColor:[UIColor yellowColor]]; }
-------------------------------------------------------------------------------------------------------------
3
iOS UICollectionView的实现
1 #pragma mark - 2 #pragma mark 委托 3 @protocol CustomCollectionDataSource<NSObject> 4 @required 5 //item的总个数 6 -(NSInteger)numberOfItemsInCollection; 7 //每一行的个数 8 -(NSInteger)numberOfItemsInRow; 9 @end 10 11 @protocol CustomCollectionDelegate<NSObject> 12 @required 13 -(CustomCollectionItem *)itemInCollectionAtPoint:(CGPoint)point collectionView:(CustomCollectionView *)collection; 14 @optional 15 -(void)itemDidSelectedAtPoint:(CGPoint)point; 16 -(BOOL)isNeedRefreshOrMore; 17 -(void)doCollectionRefresh; 18 -(void)doCollectionMore; 19 20 -(UIView *)collectionViewForHeader; 21 @end
#import <UIKit/UIKit.h> typedef enum { CS_Init, CS_More, CS_Refresh }CollectionState; @class CustomCollectionItem; @protocol CustomCollectionDataSource; @protocol CustomCollectionDelegate; @interface CustomCollectionView : UIScrollView<UIScrollViewDelegate> @property (weak, nonatomic) id<CustomCollectionDataSource> dataSource; @property (weak, nonatomic) id<CustomCollectionDelegate> customDelegate; -(CustomCollectionItem *)dequeueReusableItemWithIdentifier:(NSString *)identifier; -(void)addItemsIntoDic; -(void)itemClickedAtPoint:(CGPoint)point; -(void)reloadData; -(CustomCollectionItem *)getItemAtPoint:(CGPoint)point; @property (strong,nonatomic) UIView *headerView; @end
#import "CustomCollectionView.h" #import "CustomCollectionItem.h" #import "BaseRMView.h" @interface CustomCollectionView()
//重用池--原谅这个名字 @property (strong, nonatomic) NSMutableDictionary *contentItemDictionary;
//能够显示的item数量(以行计) @property(assign,nonatomic) NSInteger showCount; @property (assign,nonatomic) NSInteger offRowIndex;
//分割线--没有用到 默认成了10px @property (assign,nonatomic) CGFloat itemSpliteWidth;
//总行数 @property (assign,nonatomic) NSInteger rows; //item个数 @property (assign, nonatomic) NSInteger numberOfItemsInCollection;
//每行item个数 @property (assign,nonatomic) NSInteger numberOfItemsInRow;
//每一行的高度 @property (assign, nonatomic) CGFloat heightOfRow; // @property (assign, nonatomic) CGRect viewFrame; //是否第一次加载 @property (assign,nonatomic) BOOL isFirstLoad;
//上一次scrollview的offsetY,用来判断是向上滑动还是向下滑动 @property (assign,nonatomic) CGFloat lastOffsetY;
//当前最后一行的index,从0开始 @property (assign,nonatomic) NSInteger lastRowIndex;
//当前最上一行的index,从0开始 @property (assign,nonatomic) NSInteger topRowIndex; //没用 @property (assign,nonatomic) NSInteger numberOfMore; //是否需要显示刷新更多页面标志 @property (assign,nonatomic) BOOL isNeedShowMoreTag;
//刷新view @property (strong,nonatomic) BaseRMView *refreshView;
//更多view @property (strong,nonatomic) BaseRMView *moreView; @property (assign,nonatomic) CGFloat baseOffsetY; @property (assign,nonatomic) CGFloat baseCanMove; //reload之前的行数,上拉更多的时候如果用户滑动的距离超过行高会出错,用beforeRowCount来比较rows来判断新增的item需要添加的坐标 @property (assign,nonatomic) NSInteger beforeRowCount; //@property (assign,nonatomic) NSInteger firstShowCount; @end
#pragma mark - #pragma mark 页面初始化 -(id)init{ CGRect frame=[UIScreen mainScreen].applicationFrame; self=[self initWithFrame:frame]; if(self){ } return self; } - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { _viewFrame=frame; self.delegate=self; _isFirstLoad=YES; _contentItemDictionary=[[NSMutableDictionary alloc] init]; _isNeedShowMoreTag=NO; } return self; }
#pragma mark - #pragma mark 数据初始化 -(void)loadData{ if ([_dataSource respondsToSelector:@selector(numberOfItemsInCollection)]) { _numberOfItemsInCollection=[_dataSource numberOfItemsInCollection]; }else{ _numberOfItemsInCollection=0; } if([_dataSource respondsToSelector:@selector(numberOfItemsInRow)]){ _numberOfItemsInRow=[_dataSource numberOfItemsInRow]; _heightOfRow=((300.0-(_numberOfItemsInRow-1)*10)/_numberOfItemsInRow); _itemSpliteWidth=10; }else{ _numberOfItemsInRow=3;//默认为3 _heightOfRow=88; _itemSpliteWidth=18; } if ([_dataSource respondsToSelector:@selector(numberofMore)]) { _numberOfMore=[_dataSource numberofMore]; } if ([_customDelegate respondsToSelector:@selector(isNeedRefreshOrMore)]) { _isNeedShowMoreTag=[_customDelegate isNeedRefreshOrMore]; } if ([_customDelegate respondsToSelector:@selector(collectionViewForHeader)]) { _headerView=[_customDelegate collectionViewForHeader]; if (![self.subviews containsObject:_headerView]) { [self addSubview:_headerView]; } } //计算行数 _rows=ceil((float)_numberOfItemsInCollection/_numberOfItemsInRow); CGFloat contentHeight=(_rows*_heightOfRow + (_rows+1)*10+_headerView.frame.size.height); CGFloat scrollContentHeight=contentHeight>_viewFrame.size.height?contentHeight:_viewFrame.size.height; //计算一页能显示多少行 _showCount= (NSInteger)ceil((self.frame.size.height/(_heightOfRow+10))); [self setContentSize:CGSizeMake(320, scrollContentHeight)]; //判断是否有新增行,如果有当前最上义行index+1 if (_rows!=_beforeRowCount&&_beforeRowCount!=0) { _topRowIndex++; }
//从当前最上一行开始增加showcount行的item for (int i=_topRowIndex; i<_topRowIndex+_showCount; i++) { [self creatItem:i]; } if (_isNeedShowMoreTag==YES) { if (![self.subviews containsObject:_refreshView]) { _refreshView=[[BaseRMView alloc] initWithState:Refresh]; [_refreshView setFrame:CGRectMake(0, -50, 320, 50)]; [_refreshView setBackgroundColor:[UIColor grayColor]]; [self addSubview:_refreshView];
}
if (![self.subviews containsObject:_moreView]) { _moreView=[[BaseRMView alloc] initWithState:More]; [_moreView setFrame:CGRectMake(0, self.contentSize.height, 320, 50)]; [_moreView setBackgroundColor:[UIColor grayColor]]; [self addSubview:_moreView]; }else{ [_moreView setFrame:CGRectMake(0, self.contentSize.height, 320, 50)]; } } }
-(void)layoutSubviews{
//第一次加载时初始化数据,之后不需要重新计算 if (_isFirstLoad) { [self loadData]; //offsetY基数 只在第一次移动时候,10为默认的分割线高度 _baseOffsetY=(10*(_showCount+1)+_heightOfRow*_showCount)-self.frame.size.height; //移动基数 _baseCanMove=10+_heightOfRow; _isFirstLoad=NO; _lastRowIndex=_showCount-1; _topRowIndex=0; } }
//重新加载数据,记录加载前的行数
-(void)reloadData{ _beforeRowCount=_rows; [self loadData]; }
#pragma mark - #pragma mark Item相关 -(void)creatItem:(NSInteger)rowIndex{ if ([_customDelegate respondsToSelector:@selector(itemInCollectionAtPoint:collectionView:)]) { for (int j=0; j<_numberOfItemsInRow; j++) { //判断当前个数是否超过了总个数(单数情况下) if (!(((rowIndex)*_numberOfItemsInRow+j+1)>_numberOfItemsInCollection)) { //根据委托创建item CustomCollectionItem *item=[_customDelegate itemInCollectionAtPoint:CGPointMake(rowIndex, j) collectionView:self]; //设置item的大小 [item setFrame:CGRectMake(10+_heightOfRow*j+_itemSpliteWidth*j, 10+_heightOfRow*rowIndex+10*rowIndex+_headerView.frame.size.height, _heightOfRow, _heightOfRow)]; //设置item的point坐标 item.point=CGPointMake(rowIndex, j); //在view中加入item [self addSubview:item]; } } } }
//根据重用标志(reuseidentifier)从重用池中获取item -(CustomCollectionItem *)dequeueReusableItemWithIdentifier:(NSString *)identifier{ NSArray *cellArray=[self.contentItemDictionary objectForKey:identifier]; if (cellArray.count==0) { return nil; }else{ id firstObject=[cellArray objectAtIndex:0]; if([firstObject isKindOfClass:[CustomCollectionItem class]]){ //获取item后从重用池中删除item; CustomCollectionItem *item=firstObject; [[self.contentItemDictionary objectForKey:identifier] removeObject:firstObject]; [item reset]; return item; }else{ return nil; } } }
//根据point坐标从当前item数组中获取item -(CustomCollectionItem *)getItemAtPoint:(CGPoint)point{ CustomCollectionItem *result=nil; for (id item in self.subviews) { if ([item isKindOfClass:[CustomCollectionItem class]]) { if (((CustomCollectionItem *)item).point.x==point.x && ((CustomCollectionItem *)item).point.y==point.y) { result=item; } } } return result; }
-(void)addItemToPool:(CustomCollectionItem *)item{ if([[self.contentItemDictionary allKeys] containsObject:item.reuseIdentifier]){ [[self.contentItemDictionary objectForKey:item.reuseIdentifier] addObject:item]; }else{ NSMutableArray *cellArray=[NSMutableArray arrayWithObject:item]; [self.contentItemDictionary setObject:cellArray forKey:item.reuseIdentifier]; } }
#pragma mark - #pragma mark 页面滚动 //topRowIndex ---> 当前最上一行的index(从0开始); //lastRowIndex ---> 当前最后一行的index //removeIndex ---> 当前被移除的最后一行的行数(从1开始) //addIndex ---> 在showcount基础上增加的行数 -(void)scrollViewDidScroll:(UIScrollView *)scrollView{ @try { //手指向上移动移动基数后将显示下一页面 //手指向下移动移动基数将移除最下一行 BOOL isMoveUp=TRUE;//是否向下滑 if (scrollView.contentOffset.y-_lastOffsetY>0) { isMoveUp=FALSE; }else{ isMoveUp=TRUE; } _lastOffsetY=scrollView.contentOffset.y; //刷新更多 if (scrollView.contentOffset.y==0) { if ([self.subviews containsObject:_refreshView]) { [_refreshView changeState:Refresh]; } }else if(scrollView.contentOffset.y==scrollView.contentSize.height-scrollView.frame.size.height){ if ([self.subviews containsObject:_moreView]) { [_moreView changeState:More]; } }else if (scrollView.contentOffset.y>(scrollView.contentSize.height-scrollView.frame.size.height) || scrollView.contentOffset.y<0) { if (scrollView.contentOffset.y>=(scrollView.contentSize.height-scrollView.frame.size.height+50)) { if ([self.subviews containsObject:_moreView]&&_moreView.viewState==More) { [_moreView changeState:ToMore]; } }else if (scrollView.contentOffset.y<-50){ if ([self.subviews containsObject:_refreshView]&&_refreshView.viewState==Refresh) { [_refreshView changeState:ToRefresh]; } } }else{ //判断重用 if (scrollView.contentOffset.y>_headerView.frame.size.height) { CGFloat realMove=scrollView.contentOffset.y-_headerView.frame.size.height; //增加的row坐标 初始为0 移动一个移动基数后加/减1 NSInteger addIndex=ceil((realMove-_baseOffsetY)/_baseCanMove); //删除的row坐标 初始为0 移动一个移动基数后加/减1 NSInteger removeIndex=(realMove/_baseCanMove); //手指向上移动 if (!isMoveUp) { //如果最后一行编号==增加的row坐标+1&&增加的row坐标<总行数-1 if (_lastRowIndex==addIndex+_showCount-2&&addIndex<_rows-1) { //最后一行坐标++ _lastRowIndex++; //如果最后一行坐标!=总行数;如果相等则为最后一行不需要增加 if (_lastRowIndex!=_rows) { [self creatItem:_lastRowIndex]; } } //如果删除的row坐标!=0&&删除的row坐标!=最上一行坐标&&最上一行坐标<总行数-显示行数 if (removeIndex!=0&&removeIndex!=_topRowIndex&&_topRowIndex<_rows-_showCount) { for (int i=0; i<_numberOfItemsInRow; i++) { CustomCollectionItem *item=[self getItemAtPoint:CGPointMake(removeIndex-1, i)]; if (item!=nil) { [self addItemToPool:item]; [item removeFromSuperview]; } } _topRowIndex++; } }else{//remove-->add add-->remove if (removeIndex==_topRowIndex-1) { [self creatItem:removeIndex]; _topRowIndex--; } if (addIndex!=0&&addIndex!=_lastRowIndex-_showCount+1) { if (_lastRowIndex==_rows) { _lastRowIndex--; }else{ for (int i=0; i<_numberOfItemsInRow; i++) { CustomCollectionItem *item=[self getItemAtPoint:CGPointMake(_lastRowIndex, i)]; if (item!=nil) { [self addItemToPool:item]; [item removeFromSuperview]; } } _lastRowIndex--; } } } } } } @catch (NSException *exception) { NSLog(@"customCollectionView exception %@",exception.reason); } }
#pragma mark- #pragma mark item点击 -(void)itemClickedAtPoint:(CGPoint)point{ if ([_customDelegate respondsToSelector:@selector(itemDidSelectedAtPoint:)]) { [_customDelegate itemDidSelectedAtPoint:point]; } }
#pragma mark- #pragma mark 刷新更多 -(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ if (scrollView.contentOffset.y<-50) { if (_isNeedShowMoreTag==YES&&[self.subviews containsObject:_refreshView]) { if ([_customDelegate respondsToSelector:@selector(doCollectionRefresh)]) { [_customDelegate doCollectionRefresh]; } [_refreshView changeState:EndRefresh]; } }else if (scrollView.contentOffset.y>scrollView.contentSize.height-scrollView.frame.size.height+50){ if (_isNeedShowMoreTag==YES&&[self.subviews containsObject:_moreView]) { if ([_customDelegate respondsToSelector:@selector(doCollectionMore)]) { [_customDelegate doCollectionMore]; } [_moreView changeState:EndMore]; } } }
#import <UIKit/UIKit.h> #import <objc/runtime.h> #import <Foundation/Foundation.h> @interface CustomCollectionItem : UIView<UIGestureRecognizerDelegate,NSCoding> @property (strong,nonatomic) UIImageView *backgroundImage; @property (strong,nonatomic) NSString *reuseIdentifier; @property (assign,nonatomic) CGPoint point; -(id)initWithReuseIdentifier:(NSString *)identifier; -(void)itemTaped; -(void)reset; @end
#import "CustomCollectionItem.h" #import "CustomCollectionView.h" @interface CustomCollectionItem() @property(strong,nonatomic) UIView *contentView; @end @implementation CustomCollectionItem -(id)initWithReuseIdentifier:(NSString *)identifier{ self=[super init]; if (self) { _reuseIdentifier=identifier; [self setUserInteractionEnabled:YES]; _backgroundImage= [[UIImageView alloc] init]; } return self; } -(void)setFrame:(CGRect)frame { [super setFrame:frame]; [_backgroundImage setFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)]; _backgroundImage.tag=10099; } -(void)layoutSubviews { [super layoutSubviews]; if([self viewWithTag:10099]== nil) { [self addSubview:_backgroundImage]; [self sendSubviewToBack:_backgroundImage]; } UITapGestureRecognizer *tapGR=[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(itemTaped)]; [self addGestureRecognizer:tapGR]; } -(void)itemTaped{ [(CustomCollectionView *)[self superview] itemClickedAtPoint:self.point]; } -(void)setBackgroundImage:(UIImageView *)backgroundImage { _backgroundImage=backgroundImage; } #pragma override -(void)reset{ } - (void)encodeWithCoder:(NSCoder*)coder { Class clazz = [self class]; u_int count; objc_property_t* properties = class_copyPropertyList(clazz, &count); NSMutableArray* propertyArray = [NSMutableArray arrayWithCapacity:count]; for (int i = 0; i < count ; i++) { const char* propertyName = property_getName(properties[i]); [propertyArray addObject:[NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding]]; } free(properties); for (NSString *name in propertyArray) { id value = [self valueForKey:name]; [coder encodeObject:value forKey:name]; } } - (id)initWithCoder:(NSCoder*)decoder { if (self = [super init]) { if (decoder == nil) { return self; } Class clazz = [self class]; u_int count; objc_property_t* properties = class_copyPropertyList(clazz, &count); NSMutableArray* propertyArray = [NSMutableArray arrayWithCapacity:count]; for (int i = 0; i < count ; i++) { const char* propertyName = property_getName(properties[i]); [propertyArray addObject:[NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding]]; } free(properties); for (NSString *name in propertyArray) { id value = [decoder decodeObjectForKey:name]; [self setValue:value forKey:name]; } } return self; } @end