• 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
  • 相关阅读:
    使用Pencil进行UI草图设计
    模板机制在Zend Framework
    数组/链表高效去重(算法题
    Docker常用命令总结
    vscode中的git使用
    二分搜索模板
    多年没有管理的技术博客了,即日起开始管理起技术博客
    c# office不同版本下中使用Excel
    最近在忙项目,好久不来
    中秋 国庆
  • 原文地址:https://www.cnblogs.com/independent/p/3205419.html
Copyright © 2020-2023  润新知