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