ios实用开发技巧

DWCollectionView

2017-07-28  本文已影响69人  Dawn_wdf

github传送门:https://github.com/DawnWdf/DWCollectionView

支持Carthage安装,请在Cartfile中填写

github "DawnWdf/DWCollectionView"

支持cocoaPods安装

pod "DWCollectionView"
为什么封装CollectionView

我相信一定会有人做过类似的项目,踩过类似的坑的,对很多类似的、机械似的代码表示厌烦。于是我封装了collectionView。当然我不会告诉你,同组的一个大神封装了一个tableview让我受益匪浅,燃起了自己也写一个的欲望。这充分说明了,跟着大神走,有肉吃。

封装后可以渲染哪种页面

Simulator Screen Shot - iPhone 8 Plus - 2017-11-27 at 11.39.06.png Simulator Screen Shot - iPhone 8 Plus - 2017-11-27 at 11.39.14.png

只要配置好model与cell的对应关系,只要管理数据结构就可以渲染视图了。

代码如何实现

  1. 创建collection
    就像创建一个普通UICollectionView一样,除了把类名换成DWCollectionView以外,没有其他操作。而且在不声明UICollectionViewLayout的情况下,默认添加上去,免得崩溃。
    DWCollectionView *cv= [[DWCollectionView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)) collectionViewLayout:layout];
    cv.backgroundColor = [UIColor whiteColor];
    cv.delegate = self;
    [self.view addSubview:cv];
  1. 配置model和cell的关系
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section;
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;
    [self.collectionView registerViewAndModel:^(DWCollectionDelegateMaker *maker) {
        
        maker.registerCell([TeamInfoCell class],[TeamInfo class])
        .itemSize(^(NSIndexPath *indexPath, id data){
            return CGSizeMake(100, 140);
        })
        .adapter(^(UICollectionViewCell *cell, NSIndexPath *indexPath, id data){
            TeamInfoCell *newCell = (TeamInfoCell *)cell;
            newCell.showImage = YES;
            [newCell bindData:data];
        })
        .didSelect(^(NSIndexPath *indexPath, id data){
            NSLog(@"did select block : 如果vc中实现了didSelect的代理方法,则在此block后执行");
        });
 
    }];
       //header
        maker.registerHeader([UserCenterHeaderCollectionReusableView class],[UserCenterHeaderModel class])
        .sizeConfiger(^(UICollectionViewLayout *layout,NSInteger section, id data){
            return CGSizeMake(screenW, 33);
        })
        .adapter(^(UICollectionReusableView *reusableView,NSIndexPath *indexPath, id data) {
            UserCenterHeaderCollectionReusableView *view = (UserCenterHeaderCollectionReusableView *)reusableView;
            [view bindData:data];
        });
        
        //footer
        maker.registerFooter([UICollectionReusableView class],[NSString class])
        .sizeConfiger(^(UICollectionViewLayout *layout,NSInteger section, id data){
            return CGSizeMake(screenW, 10);
        })
        .adapter(^(UICollectionReusableView *reusableView,NSIndexPath *indexPath, id data) {
            reusableView.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.5];
        });

到此,一个简单的collectionView就全部完毕。总结一下就三个步骤:

  1. 创建
  2. 绑定
  3. 添加数据源

其他的代理方法

我在封装的时候,希望对原有collectionView的侵入性最小。所以你会发现,只有上面提到的常用的代理方法是使用block的形式封装在了一起。如果collectionView的功能比较多,需要实现其他的代理方法,比如:- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath ;还是需要自己在vc中写好的。
所以,如果对layout有特殊的要求,依然可以实现相应的代理方法。如:

#pragma mark - UICollectionViewDelegateFlowLayout

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
    return UIEdgeInsetsMake(10, 10, 10, 10);
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
    return 10;
}

或者在创建的时候直接使用

 UICollectionViewFlowLayout *sysLayout = [[UICollectionViewFlowLayout alloc] init];
 sysLayout.estimatedItemSize = CGSizeMake(100, 150);
 sysLayout.minimumLineSpacing = 50;

这些都是使用UICollectionView标准的方式,不赘述。

题外话:DWFlowLayout(自定义布局-瀑布流),不在封装范围内。

缺憾

为了能更好的封装代理方法,我将代理做了重定向。所以,collectionView的代理在执行的时候用的并不是声明时指向的代理VC,而是我自己的代理类DWCollectionDelegate。我只是在DWCollectionDelegate记录了VC,并在对应的方法中调用了一次VC的方法。
然而collectionView遵循的协议有四个:

如果把所有的协议的代理方法都写出来,那就是个天文数字。
所以我将不常用的代理方法使用runtime的方法做了转换。
具体的代码如下:

for (int i = 0; i < protocolMethodCount; i++) {
                    struct objc_method_description protocolObject = protocolDes[i];
                    
                    SEL selector = protocolObject.name;
                    //originalDelegate是否实现此方法
                    BOOL isOriginalResponse = class_respondsToSelector(original , selector);
                   if (isOriginalResponse) {
                        Method originalMethod = class_getInstanceMethod(original, selector);
                        class_replaceMethod(aclass, selector, class_getMethodImplementation(original, selector), method_getTypeEncoding(originalMethod));
                    }
                }

注释中的originalDelegate代表的就是声明时设置的代理VC。‘当前类’代表的就是DWCollectionDelegate。从代码中可以看出我实际上是将两个类的代理方法的imp互换了。所以就会出现一个问题,例如代理方法-(void)scrollViewDidScroll:(UIScrollView *)scrollView在当前类中不存在,但是在originalDelegate中存在了,替换了imp后,在scrollViewDidScroll方法中的self就成了DWCollectionDelegate。所以这样的代理方法中就要判断一下self是哪个类。同时,由于DWCollectionDelegate已经添加了这个代理方法,如果在其他的VC中不需要执行这个代理方法,它会去实现过的方法中找,所以也需要判断delegate.originalDelegate是否为你需要的vc。逊毙了!!!low货!!

UserCenterViewController.m中实现代理方法

-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    CGPoint offset = scrollView.contentOffset;
    if ([self isKindOfClass:[DWCollectionDelegate class]]) {
        DWCollectionDelegate *delegate = (DWCollectionDelegate *)self;
        id original = delegate.originalDelegate;
        if ([original isKindOfClass:[UserCenterViewController class]]) {
            UserCenterViewController *vc = (UserCenterViewController *)original;
            [vc updateUserInforView:scrollView];
            [vc updateNav:offset];
        }

    }else if([self isKindOfClass:[UserCenterViewController class]]){
        [self updateUserInforView:scrollView];
        [self updateNav:offset];
    }
}

为了方便,我将这部分判断做成了宏定义

#define DW_CheckSelfClass(calssName) \
calssName *trueSelf = self; \
if ([self isKindOfClass:[DWCollectionDelegate class]]) { \
DWCollectionDelegate *delegate = (DWCollectionDelegate *)self; \
id original = delegate.originalDelegate; \
if ([original isKindOfClass:[calssName class]]) { \
calssName *vc = (calssName *)original; \
trueSelf = vc;\
}else{ \
return; \
} \
}else if([self isKindOfClass:[calssName class]]){ \
trueSelf = self; \
} \
\

所以,新的代码为

-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    CGPoint offset = scrollView.contentOffset;
    DW_CheckSelfClass(UserCenterViewController);
    [trueSelf updateUserInforView:scrollView];
    [trueSelf updateNav:offset];
}

这个问题暂时还没有找到解决的方案。如果有大神出手,我会更新。如果路过的大神有方法,还请路见不平一声吼。多谢!

在此列出已经在DWCollectionDelegate实现的代理方法,在以下方法中可以不去判断self的类型。
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section 
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section 
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath 
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
- (CGSize)collectionView:(UICollectionView *)collectionView
                  layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath 
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView //不需要实现

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section //不需要实现

- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath//不需要实现

- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath

封装思路

PS

暴露两个NSObject的扩展类

上一篇 下一篇

猜你喜欢

热点阅读