iOS UICollectionView的实现
ios的UICollectionView并不能在iOS6之前的版本中使用,为了兼容之前的版本需要自定义UICollectionView。写完之后发现人家已经有开源了,下过来看了看发现我是用UIScrollerView的委托真是多此一举,完全可以用layout来实现嘛。我在判断重用的时候用了一大堆if没有别人写的简洁明了。
首先是定义委托,需要用户传入collection总item的总数与每一行中item的个数。其余的与UITableView的委托基本一致。
isNeedRefreshOrMore方法用来判断用户使用需要下拉刷新上拉更多的功能,返回YES就使用。
doCollectionRefresh即为响应下拉刷新事件。更多同样。。
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
在同文件中定义了页面状态的枚举,用来区分Collcetion的状态。同时定义了一些public方法
#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