美文网首页iOS小筑天生不是作曲家ios 进阶
UICollectionView(一)——整体总结

UICollectionView(一)——整体总结

作者: Wang66 | 来源:发表于2015-12-05 03:27 被阅读118661次

    前言

    这几天有时间看了下UICollectionView的东西,才发觉它真的非常强大,很有必要好好学习学习。以前虽然用过几次,但没有系统的整理总结过。这两天我为UICollectionView做一个比较全面的整理。包括基本使用自定义布局自定义插入删除动画自定义转场动画等几部分。好了,开始。

    UICollectionView相对于UITableView可以说是青出于蓝而胜于蓝,它和UITableView很相似,但它要更加强大。
    UITableView的布局形式比较单一,局限于行列表,而UICollectionView的强大之处在于把视图布局分离出来成为一个独立的类,你想实现怎样的视图布局,就子类化这个类并在其中实现。

    UICollectionView基础

    • UICollectionViewFlowLayout:视图布局对象(流视图:一行排满,自动排到下行),继承自UICollectionViewLayout。
      UICollectionViewLayout有个collectionView属性,
      所有的视图布局对象都继承自UICollectionViewLayout。若我们要自定义布局对象,我们一般继承UICollectionViewFlowLayout就可以了。
    • 需要实现三个协议;UICollectionViewDataSource(数据源)、UICollectionViewDelegateFlowLayout(视图布局)、UICollectionViewDelegate。
      可以看得出,除了视图布局,UICollectionView几乎和UITableView一样,但这也正是它的强大之处。
    1.创建UICollectionView视图
    - (void)loadCollectionView
    {
        _customLayout = [[CustomCollectionViewLayout alloc] init]; // 自定义的布局对象
        _collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:_customLayout];
        _collectionView.backgroundColor = [UIColor whiteColor];
        _collectionView.dataSource = self;
        _collectionView.delegate = self;
        [self.view addSubview:_collectionView];
        
        // 注册cell、sectionHeader、sectionFooter
        [_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:cellId];
        [_collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:headerId];
        [_collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:footerId];
    }
    

    需要注意的是这几行代码的位置,及const的位置。(我经常搞乱)

    @implementation YWViewController
    
    // 注意const的位置
    static NSString *const cellId = @"cellId";
    static NSString *const headerId = @"headerId";
    static NSString *const footerId = @"footerId";
    
    
    - (void)viewDidLoad
    {
    
    2.实现UICollectionViewDataSource的几个代理方法
    
    #pragma mark ---- UICollectionViewDataSource
    
    - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
    {
        return 1;
    }
    
    
    - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
    {
        return _section0Array.count;
    }
    
    
    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
    {
        UICollectionViewCell *cell = [_collectionView dequeueReusableCellWithReuseIdentifier:cellId forIndexPath:indexPath];
        cell.backgroundColor = [UIColor purpleColor];
        
        return cell;
    }
    
    // 和UITableView类似,UICollectionView也可设置段头段尾
    - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
    {
    
        if([kind isEqualToString:UICollectionElementKindSectionHeader])
        {
            UICollectionReusableView *headerView = [_collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:headerId forIndexPath:indexPath];
            if(headerView == nil)
            {
                headerView = [[UICollectionReusableView alloc] init];
            }
            headerView.backgroundColor = [UIColor grayColor];
            
            return headerView;
        }
        else if([kind isEqualToString:UICollectionElementKindSectionFooter])
        {
            UICollectionReusableView *footerView = [_collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:footerId forIndexPath:indexPath];
            if(footerView == nil)
            {
                footerView = [[UICollectionReusableView alloc] init];
            }
            footerView.backgroundColor = [UIColor lightGrayColor];
            
            return footerView;
        }
        
        return nil;
    }
    
    - (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath
    {
        return YES;
    }
    
    
    - (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath
    {
        
    }
    
    
    
    
    #pragma mark ---- UICollectionViewDelegateFlowLayout
    
    - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
    {
        return (CGSize){cellWidth,cellWidth};
    }
    
    
    - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
    {
        return UIEdgeInsetsMake(5, 5, 5, 5);
    }
    
    
    - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
    {
        return 5.f;
    }
    
    
    - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
    {
        return 5.f;
    }
    
    
    - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
    {
        return (CGSize){ScreenWidth,44};
    }
    
    
    - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section
    {
        return (CGSize){ScreenWidth,22};
    }
    
    
    
    
    #pragma mark ---- UICollectionViewDelegate
    
    - (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath
    {
        return YES;
    }
    
    // 点击高亮
    - (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath
    {
        UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
        cell.backgroundColor = [UIColor greenColor];
    }
    
    
    // 选中某item
    - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
    {
        
    }
    
    
    // 长按某item,弹出copy和paste的菜单
    - (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath
    {
        return YES;
    }
    
    // 使copy和paste有效
    - (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender
    {
        if ([NSStringFromSelector(action) isEqualToString:@"copy:"] || [NSStringFromSelector(action) isEqualToString:@"paste:"])
        {
            return YES;
        }
        
        return NO;
    }
    
    //
    - (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender
    {
        if([NSStringFromSelector(action) isEqualToString:@"copy:"])
        {
    //      NSLog(@"-------------执行拷贝-------------");
            [_collectionView performBatchUpdates:^{
                [_section0Array removeObjectAtIndex:indexPath.row];
                [_collectionView deleteItemsAtIndexPaths:@[indexPath]];
            } completion:nil];
        }
        else if([NSStringFromSelector(action) isEqualToString:@"paste:"])
        {
            NSLog(@"-------------执行粘贴-------------");
        }
    }
    

    UICollectionView自定义布局

    要自定义UICollectionView布局,就要子类化UICollectionViewLayout,然后重写它的一些方法以达到我们自定义布局的需求。下来我们来看看UICollectionViewLayout类里一些比较重要的方法:

    • - (void)prepareLayout;为layout显示做准备工作,你可以在该方法里设置一些属性。
    • - (CGSize)collectionViewContentSize;返回layout的size。
    • *- (NSArray )layoutAttributesForElementsInRect:(CGRect)rect;返回在collectionView的可见范围内(bounds)所有item对应的layoutAttrure对象装成的数组。collectionView的每个item都对应一个专门的UICollectionViewLayoutAttributes类型的对象来表示该item的一些属性,比如bounds,size,transform,alpha等。
    • **- (UICollectionViewLayoutAttributes )layoutAttributesForItemAtIndexPath:(NSIndexPath )indexPath;传入indexPath,返回该indexPath对应的layoutAtture对象。
    • **- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds; **当当前layout的布局发生变动时,是否重写加载该layout。默认返回NO,若返回YES,则重新执行这俩方法:
    • - (void)prepareLayout;
    • - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect;
    • - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity;返回layout“最终”的偏移量,何谓“最终”,手指离开屏幕时layout的偏移量不是最终的,因为它有惯性,当它停止时才是“最终”偏移量。

    下面这两个方法一般用于自定义插入删除时的动画,后面再说。

    • **- (UICollectionViewLayoutAttributes )initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath )itemIndexPath;

    • **- (nullable UICollectionViewLayoutAttributes )finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath )itemIndexPath;

    本Demo的代码虽然子类化了UICollectionViewLayout,但是主要是用于自定义插入删除动画,所以本段没什么代码展示。


    UICollectionView插入删除的操作及动画

    插入删除的操作

    添加在哪触发:

        UIBarButtonItem *btnItem = [[UIBarButtonItem alloc] initWithTitle:@"添加"
                                                                    style:UIBarButtonItemStylePlain
                                                                   target:self
                                                                   action:@selector(addItemBtnClick:)];
        
        self.navigationItem.rightBarButtonItem = btnItem;
    

    添加的实现:

    // 添加(插入item)
    - (void)addItemBtnClick:(UIBarButtonItem *)btnItem
    {
        [_collectionView performBatchUpdates:^{
            // 构造一个indexPath
            NSIndexPath *indePath = [NSIndexPath indexPathForItem:_section0Array.count inSection:0];
            [_collectionView insertItemsAtIndexPaths:@[indePath]]; // 然后在此indexPath处插入给collectionView插入一个item
            [_section0Array addObject:@"x"]; // 保持collectionView的item和数据源一致
        } completion:nil];
    }
    

    因为是练习Demo,所以暂时把删除的触发源写在了长按某Item弹出菜单的copy按钮里。实际中你可以自定义UICollectionViewCell,添加长按手势,长按抖动出现叉号,然后删除等,随你怎么做。

    // copy and paste 的实现
    - (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender
    {
       if([NSStringFromSelector(action) isEqualToString:@"copy:"])
       {
    //      NSLog(@"-------------执行拷贝-------------");
           [_collectionView performBatchUpdates:^{
               [_section0Array removeObjectAtIndex:indexPath.row];
               [_collectionView deleteItemsAtIndexPaths:@[indexPath]];
           } completion:nil];
       }
       else if([NSStringFromSelector(action) isEqualToString:@"paste:"])
       {
           NSLog(@"-------------执行粘贴-------------");
       }
    }
    

    插入删除的动画

    上面已经提到了在UICollectionViewLayout类中有两个用于自定义动画的方法,两个方法分别表示动画的起始状态和终止状态,我们可以分别在方法里设置layoutAttrure来实现某种动画效果。

    苹果选择了一种安全的途径去实现一个简单的淡入淡出动画作为所有布局的默认动画。如果你想实现自定义动画,最好的办法是子类化 UICollectionViewFlowLayout 并且在适当的地方实现你的动画。

    一般来说,我们对布局属性从初始状态到结束状态进行线性插值来计算 collection view 的动画参数。然而,新插入或者删除的元素并没有最初或最终状态来进行插值。要计算这样的 cells 的动画,collection view 将通过 initialLayoutAttributesForAppearingItemAtIndexPath: 以及 finalLayoutAttributesForDisappearingItemAtIndexPath: 方法来询问其布局对象,以获取最初的和最后的属性。苹果默认的实现中,对于特定的某个 indexPath,返回的是它的通常的位置,但 alpha 值为 0.0,这就产生了一个淡入或淡出动画。

    简而言之,就是苹果自带了插入删除时Item的淡入淡出的动画,若你想自定义更炫的动画,就子类化UICollectionViewFlowLayout类,并重写以下两个方法:

    // 初始状态
    - (nullable UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
    {
        UICollectionViewLayoutAttributes *attr = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
        attr.center = CGPointMake(CGRectGetMidX(self.collectionView.bounds), CGRectGetMaxY(self.collectionView.bounds));
        attr.transform = CGAffineTransformRotate(CGAffineTransformMakeScale(0.2, 0.2), M_PI);
    
        return attr;
    }
    
    
    // 终结状态
    - (nullable UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
    {
        UICollectionViewLayoutAttributes *attr = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
            attr.alpha = 0.0f;
        
        return attr;
    }
    

    insert&delete.gif

    UICollectionView的转场动画

    http://objccn.io/issue-12-5/

    相关文章

      网友评论

      • _moses:您好,我想问一下,插入删除动画能修改时间吗?我看苹果好像没提供啊?!
      • 云画的跃光:我想請問下,我是按照你這樣創建的,為什麼我的沒有加載進去,沒有顯示collectionview
      • 云画的跃光:你好,我想请问下,当点击cell时,改变颜色,但是我的点击没有效果,用的是自定义cell
      • AbnerZhang:你好我在做collectionView的时候, 设置了collectionView的列间距为0,但是会在6, 6p的大机型上依然会有间隔线,可是在5机型上确没有, 你遇到吗,希望研究帮忙解决一下
        Wang66:@AbnerZhang 抱歉,这个问题我知道还没注意过
      • 056570e5904e:大神我问下我要同时设置组头和collection header应该怎么在代理方法里判断?
      • 经文纬武:正好用到,写的很好
      • Alan龙马:不错不错,mark
      • 1c7078515d0c:你是用什么元软件写的文章? :blush:
        mengkeer:这是用markdown格式写的 简书自带的就有 不过要切换成markdown格式
      • a4514b0d97ae:请问一下,我在每个item上设置图片,怎么根据宽高比来设置item的高度
      • 160ace3f76c1:写的很完整,正好用上,感谢楼主
      • Laughingg:你可以把博客的格式调整一下,内容丰富一下。赞就更多。
      • 呜啦啦啦拉拉:你好,想问一下collectionviewcell怎么实现自适应size。
      • 呜啦啦啦拉拉:你好,想问一下collectionview怎么实现自适应size。
      • e8dfad189e00:对新接触的有很大帮助 学习了
      • 0o冻僵的企鹅o0:1.8w的阅读量,竟然只有38个赞。我先赞为敬~ :smile:
        Wang66:@0o冻僵的企鹅o0 谢谢,可能写得还不太好吧:pray:
      • b439515008f6:学习了,感谢你,这些东西好多都是现在才发现的特性。共勉。
      • i赵磊:恩恩。谢谢
      • i赵磊:我想咨询下你是怎么把代码插入到简书的。我的插入都是白色背景。我也想发表的是代码块。

        Wang66:@i赵磊
        ```
        代码。。。

        ```
      • 7e33bcf02f7f:你好有demo吗,学习下collectionniez
        Wang66:@恏玖芣见 没有demo,上面基本是全部代码了。
      • 蛋蛋怪噜饭kokiyu:亲,可以看下你的github或者cocochina的这段代码……?
        38b0cc97245a:@大小尘 是啊...他没贴出CustomCollectionViewLayout 的代码
        e668a4da202c:CustomCollectionViewLayout 最重要的自定义布局的代码没有给出来
        Wang66:@蛋蛋怪噜饭kokiyu 我其他地方没存有代码,文中是全部代码吧。
      • 一号线:请问,我用代码布局collection view的section header时 会出现重用的情况,我把自定义header内部控件用懒加载实现 就不会重载这个问题了 。。是不是一般都用的懒加载啊
        清歌而行:我也出现了,有解决方法吗?不想再去写个UICollectionReusableView的类:flushed:
        JennonL:我也一样,出现重用情况。。
        Wang66:@一号线 这个应该是section header和cell一样也要复用吧?是不是?具体要看代码啊。

      本文标题:UICollectionView(一)——整体总结

      本文链接:https://www.haomeiwen.com/subject/ubishttx.html