iOS-UICollectionView

时间:2022-11-01 04:42:58

1------------------------------------------------------------------------------------------------------------------------

本章通过先总体介绍UICollectionView及其常用方法,再结合一个实例,了解如何使用UICollectionView。

UICollectionView 和 UICollectionViewController 类是iOS6 新引进的API,用于展示集合视图,布局更加灵活,可实现多列布局,用法类似于UITableView 和 UITableViewController 类。

使用UICollectionView 必须实现UICollectionViewDataSource,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout这三个协议。

下面先给出常用到的一些方法。(只给出常用的,其他的可以查看相关API)

  1. #pragma mark -- UICollectionViewDataSource
  1. //定义展示的UICollectionViewCell的个数
  2. -(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
  3. {
  4. return 30;
  5. }
  1. //定义展示的Section的个数
  2. -(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
  3. {
  4. return 1;
  5. }
  1. //每个UICollectionView展示的内容
  2. -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
  3. {
  4. static NSString * CellIdentifier = @"GradientCell";
  5. UICollectionViewCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
  6. cell.backgroundColor = [UIColor colorWithRed:((10 * indexPath.row) / 255.0) green:((20 * indexPath.row)/255.0) blue:((30 * indexPath.row)/255.0) alpha:1.0f];
  7. return cell;
  8. }
  1. #pragma mark --UICollectionViewDelegateFlowLayout
  1. //定义每个UICollectionView 的大小
  2. - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
  3. {
  4. return CGSizeMake(96, 100);
  5. }
  1. //定义每个UICollectionView 的 margin
  2. -(UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
  3. {
  4. return UIEdgeInsetsMake(5, 5, 5, 5);
  5. }
  1. #pragma mark --UICollectionViewDelegate
  1. //UICollectionView被选中时调用的方法
  2. -(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
  3. {
  4. UICollectionViewCell * cell = (UICollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
  5. cell.backgroundColor = [UIColor whiteColor];
  6. }
  1. //返回这个UICollectionView是否可以被选择
  2. -(BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath
  3. {
  4. return YES;
  5. }

下面通过一个例子具体介绍下。(例子来自网络。但是是通过第三方获得的,无法取得链接。还望见谅。)

iOS CollectionView的出现是一大福利,再也不用用TableView来定义复杂的多栏表格了,用法与Table类似,只是Cell必须自己添加,无默认模式

由于CollectionView没有默认的Cell布局,所以一般还是自定义方便又快捷

一、自定义Cell

1、新建类CollectionCell继承自UICollectionViewCell

iOS-UICollectionView

2、新建Xib,命名为CollectionCell.xib

iOS-UICollectionView

a.选中CollectionCell.xib删掉默认的View,从控件中拖一个Collection View Cell(图3)到画布中,设置大小为95*116;

iOS-UICollectionView

b.选中刚刚添加的Cell,更改类名为CollectionCell,如图4

iOS-UICollectionView

c.在CollectionCell.xib的CollectionCell中添加一个ImageView和一个Label(图5)

iOS-UICollectionView

d.创建映射, 图6,图7

iOS-UICollectionView

iOS-UICollectionView

e.选中CollectionCell.m , 重写init方法

  1. - (id)initWithFrame:(CGRect)frame
  2. {
  3. self = [super initWithFrame:frame];
  4. if (self)
  5. {
  6. // 初始化时加载collectionCell.xib文件
  7. NSArray *arrayOfViews = [[NSBundle mainBundle] loadNibNamed:@"CollectionCell" owner:self options:nil];
  8. // 如果路径不存在,return nil
  9. if (arrayOfViews.count < 1)
  10. {
  11. return nil;
  12. }
  13. // 如果xib中view不属于UICollectionViewCell类,return nil
  14. if (![[arrayOfViews objectAtIndex:0] isKindOfClass:[UICollectionViewCell class]])
  15. {
  16. return nil;
  17. }
  18. // 加载nib
  19. self = [arrayOfViews objectAtIndex:0];
  20. }
  21. return self;
  22. }

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

iOS-UICollectionView

4、选中CollectionView的属性,可以修改其属性,比如是垂直滑动,还是水平滑动,选择Vertical或Horizontal

5、选中CollectionViewCell,修改Class,继承自CollectionCell

iOS-UICollectionView

5、在ViewDidLoad方法中声明Cell的类,在ViewDidLoad方法中添加,此句不声明,将无法加载,程序崩溃

其中,CollectionCell是这个Cell的标识(之前几步已经定义过了。 )

  1. [self.collectionView registerClass:[CollectionCell class] forCellWithReuseIdentifier:@"CollectionCell"];

6、在ViewController.h中声明代理

  1. @interface ViewController : UIViewController<UICollectionViewDataSource,UICollectionViewDelegate>

7、在.m文件中实现代理方法

  1. //每个section的item个数
  2. -(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
  3. {
  4. return 12;
  5. }
  6. -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
  7. {
  8. CollectionCell *cell = (CollectionCell *)[collectionView dequeueReusableCellWithReuseIdentifier:@"CollectionCell" forIndexPath:indexPath];
  9. //图片名称
  10. NSString *imageToLoad = [NSString stringWithFormat:@"%d.png", indexPath.row];
  11. //加载图片
  12. cell.imageView.image = [UIImage imageNamed:imageToLoad];
  13. //设置label文字
  14. cell.label.text = [NSString stringWithFormat:@"{%ld,%ld}",(long)indexPath.row,(long)indexPath.section];
  15. return cell;
  16. }

8 。效果如图10

iOS-UICollectionView

点击某项后跳转事件与UITableView类似,实现代理方法

  1. -(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
------------------------------------------------------------------------------------------------------------------------------------
2

UICollectionView基础

 

初始化部分:

iOS-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];
iOS-UICollectionView

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,该方法必须实现:

iOS-UICollectionView
- (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;
}
iOS-UICollectionView

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

添加页眉和页脚以前需要注册类和标识:


添加补充视图的代码示例:

iOS-UICollectionView
[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;
}
iOS-UICollectionView

MyHeadView.h

#import <UIKit/UIKit.h>

@interface MyHeadView : UICollectionReusableView
- (void) setLabelText:(NSString *)text;
@end

MyHeadView.m

iOS-UICollectionView
#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
iOS-UICollectionView

在注册Cell和补充视图时,也可以用新建xib文件的方式:

iOS-UICollectionView
[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"];
iOS-UICollectionView

用这种方式注册后,甚至可以不用新建类去绑定这个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的尺寸

iOS-UICollectionView
- (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);
}
}
iOS-UICollectionView

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;

设定collectionView(指定区)的边距

iOS-UICollectionView
- (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);
}
}
iOS-UICollectionView

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section

设定指定区内Cell的最小行距,也可以直接设置UICollectionViewFlowLayout的minimumLineSpacing属性

iOS-UICollectionView
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
{
if(section==0)
{
return 10.0;
}
else
{
return 20.0;
}
}
iOS-UICollectionView

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section;

设定指定区内Cell的最小间距,也可以直接设置UICollectionViewFlowLayout的minimumInteritemSpacing属性

iOS-UICollectionView
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
{
if(section==0)
{
return 10.0;
}
else
{
return 20.0;
}
}
iOS-UICollectionView

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

事件的处理顺序如下:

  1. 手指按下
  2. shouldHighlightItemAtIndexPath (如果返回YES则向下执行,否则执行到这里为止)
  3. didHighlightItemAtIndexPath (高亮)
  4. 手指松开
  5. didUnhighlightItemAtIndexPath (取消高亮)
  6. shouldSelectItemAtIndexPath (如果返回YES则向下执行,否则执行到这里为止)
  7. didSelectItemAtIndexPath (执行选择事件)

如果只是简单实现点击后cell改变显示状态,只需要在cellForItemAtIndexPath方法里返回cell时,指定cell的selectedBackgroundView:

iOS-UICollectionView
- (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;
}
iOS-UICollectionView

如果要实现点击时(手指未松开)的显示状态与点击后(手指松开)的显示状态,则需要通过上面提到的方法来实现:

iOS-UICollectionView
- (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的实现

 
ios的UICollectionView并不能在iOS6之前的版本中使用,为了兼容之前的版本需要自定义UICollectionView。写完之后发现人家已经有开源了,下过来看了看发现我是用UIScrollerView的委托真是多此一举,完全可以用layout来实现嘛。我在判断重用的时候用了一大堆if没有别人写的简洁明了。
 
首先是定义委托,需要用户传入collection总item的总数与每一行中item的个数。其余的与UITableView的委托基本一致。
isNeedRefreshOrMore方法用来判断用户使用需要下拉刷新上拉更多的功能,返回YES就使用。
doCollectionRefresh即为响应下拉刷新事件。更多同样。。
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
iOS-UICollectionView
在同文件中定义了页面状态的枚举,用来区分Collcetion的状态。同时定义了一些public方法
iOS-UICollectionView
#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
iOS-UICollectionView
iOS-UICollectionView
#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
iOS-UICollectionView
iOS-UICollectionView
#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;
}
iOS-UICollectionView
iOS-UICollectionView
#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)];
}
}
}
iOS-UICollectionView
iOS-UICollectionView
-(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;
}
}
iOS-UICollectionView
//重新加载数据,记录加载前的行数
-(void)reloadData{
_beforeRowCount=_rows;
[self loadData];
}
iOS-UICollectionView
#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];
}
}
}
}
iOS-UICollectionView
iOS-UICollectionView
//根据重用标志(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;
}
}
}
iOS-UICollectionView
iOS-UICollectionView
//根据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;
}
iOS-UICollectionView
iOS-UICollectionView
-(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];
}
}
iOS-UICollectionView
iOS-UICollectionView
#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);
}
}
iOS-UICollectionView
iOS-UICollectionView
#pragma mark-
#pragma mark item点击
-(void)itemClickedAtPoint:(CGPoint)point{
if ([_customDelegate respondsToSelector:@selector(itemDidSelectedAtPoint:)]) {
[_customDelegate itemDidSelectedAtPoint:point];
}
}
iOS-UICollectionView
iOS-UICollectionView
#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];
}
}
}
iOS-UICollectionView
iOS-UICollectionView
#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
iOS-UICollectionView
iOS-UICollectionView
#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