美文网首页iOS
UICollectionView & UICollect

UICollectionView & UICollect

作者: FlowerKanZhe | 来源:发表于2016-05-22 16:55 被阅读138次
    视觉

    UICollectionView与UITableView十分的类似,但是UICollection的功能更加的强大,其强大之处在与UICollectionViewLayout,这个类的作用是给cell提供布局信息。因此,不管什么样的布局,都可以通过UICollectionViewLayout实现。而本文主要说说UICollectionView的一些相关使用,如:cell的移动和cell的自定义编辑菜单,还有布局的变化(即CollectionView中Layout对象的改变)。

    1.cell自定义编辑菜单

    Simulator Screen Shot 2016年5月22日 15.13.28.png

    如上图所示,其中的copy、custom按钮对应的菜单就是cell自带的编辑菜单。当我们长按cell中对应的某个项目的时候,就会弹出编辑菜单。编辑菜单是一个UIMenuController单例,而其中的item对应的是UIMenuItem。要实现编辑菜单也十分的简单,只需要按照如下的步骤即可:

    第一步:实现如下三个代理方法

    /**
     *  当我们长按cell的时候会调用此方法,告知是否可以显示编辑菜单
     */
    - (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath {
        return YES;
    }
    
    /**
     *  此方法需实现,告知我们需要显示的item(即是否显示copy、cut等item,在ios7之后是在cell中告知的)
     */
    - (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
        return NO;
    }
    
    /**
     *  此方法也需实现,但实现类容可无
     */
    - (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
    }
    

    第二步:定制自己的item,本步骤可有可无,全凭是否添加自定义item

    // 实例化一个item
    UIMenuItem * customItem = [[UIMenuItem alloc] initWithTitle:@"Custom" action:@selector(customItem:)];
    // 添加到UIMenuController中去
    [[UIMenuController sharedMenuController] setMenuItems:@[customItem]];
    

    注意:此处的selector对应的方法必须放在自定义cell的实现文件中。

    第三步:cell中添加必须的方法

    // 此方法保证UIMenuController能称为第一响应者,能被显示出来
    - (BOOL)canBecomeFirstResponder {
        return YES;
    }
    
    // 这里决定要显示的item,在这里可以过滤到系统的item
    - (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
        // 显示系统自带的copy item
        if (action == @selector(copy:)) {
            return YES;
        }
        
        if (action == @selector(customItem:)) {
            return YES;
        }
        
        return NO;
    }
    
    - (void)customItem:(UIMenuController *)sender {
    }
    
    - (void)copy:(UIMenuController *)sender {
    }
    

    至此,自定义cell的编辑菜单就算完成了,当然点击item需要执行的操作应该控制器在完成,这里在selector方法中应该通过代理回到控制器中去完成。

    2.Layout Object 的改变

    首先我们来看看效果

    2016-05-22 16_02_50.gif

    Layout Object对象的改变有两种方式,第一种是通过代码方式,这种方法简单,可以带有动画,但是不能实现动画的进度控制。第二种方式是一种基于手势的改变LayoutObject对象,这种方式对应的动画进度是可控的。但是,在改变LayoutObject对象之前,要确保你有两种布局方式,即两个Layout对象。

    本文采用的是自定义的两个布局对象为:环形布局、堆叠式布局

    首先看一看第一种方式:

    - (IBAction)changeItem:(id)sender {
        static BOOL isStack = NO;
        isStack = !isStack;
        if (isStack) {
            [self.collectionView setCollectionViewLayout:self.stackLayout animated:YES];
        } else {
            [self.collectionView setCollectionViewLayout:self.cycleLayout animated:YES];
        }
    }
    

    这里,当我点击按钮的时候,布局就动画的从cycleLayout布局变化到了stackLayout布局,在此点击就换回来了。第一种方法的实现就完成了。

    接下来看看第二种方式:

    看看动画

    2016-05-22 16_22_55.gif

    首先给UICollectionView添加一个屏幕边缘手势:

    UIScreenEdgePanGestureRecognizer * edgePan = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(edgePan:)];
        edgePan.edges = UIRectEdgeLeft;
        [self.view addGestureRecognizer:edgePan];
    
    

    其次实现手势响应方法

    - (void)edgePan:(UIScreenEdgePanGestureRecognizer *)edgePan {
        
        // 计算拖拽的进度
        CGFloat progress = [edgePan translationInView:self.view].x / 200;
        progress = MIN(1, MAX(0, progress));
        
        if (edgePan.state == UIGestureRecognizerStateBegan) { // 是手势开始
            // 告知collection开始交互变化Layout,以及交互变化layout完成调用的block
            self.transitionLayout = [self.collectionView startInteractiveTransitionToCollectionViewLayout:self.stackLayout completion:^(BOOL completed, BOOL finished) {
                NSLog(@"完成");
            }];
        } else if (edgePan.state == UIGestureRecognizerStateChanged) { // 如果是手势进行中,将进度告知collection的开始时返回的对象
            
            self.transitionLayout.transitionProgress = progress;
            [self.transitionLayout invalidateLayout];
        }  else if (edgePan.state == UIGestureRecognizerStateEnded) { // 如果是手势结束
            if (progress < 0.5) { // 如果完成进度小于0.5,就取消本次layout对象的转变
            
            [self.collectionView cancelInteractiveTransition];
        } else {
            
            [self.collectionView finishInteractiveTransition];
        }
        }
    }
    
    

    当我们的手指从屏幕边缘划过的时候,如果是刚开始滑动,就告知UICollectionView开始交互变化布局,调用UICollectInView的startInteractiveTransitionToCollectionViewLayout:方法,此方法会返回一个系统提供的用于变化布局的对象,将此对象保存下来。当滑动进行中,我们通过滑动的距离和规定的滑动距离向比较,得出滑动完成的进度,并把进度告知系统提供的用于变化布局的对象。当滑动完成时,通过滑动完成进度与0.5的比较告知是否完成交互变化布局。

    3.cell的手势交互移动

    不多说,还是先看看效果:

    2016-05-22 16_46_28.gif

    直接上代码:

    第一步:给cell添加tap手势

    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
        
        KVCollectionViewCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellReuseIdentifier forIndexPath:indexPath];
        cell.indexLabel.text = [NSString stringWithFormat:@"我是国民好男人%ld", indexPath.row];
        
        // 给cell添加手势
        UIPanGestureRecognizer * panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
        [cell addGestureRecognizer:panGesture];
        return cell;
    }
    
    

    第二步:实现手势响应方法

    - (void)pan:(UIPanGestureRecognizer *)pan {
        
        UIView * view = pan.view;
        
        NSIndexPath * indexPath = [self.collectionView indexPathForCell:(KVCollectionViewCell *)view];
        
        CGPoint curentPoint = [pan locationInView:self.collectionView];
        
        if (pan.state == UIGestureRecognizerStateBegan) { // 手势开始
            // 告知collectionView开始交互移动
            [self.collectionView beginInteractiveMovementForItemAtIndexPath:indexPath];
        } else if (pan.state == UIGestureRecognizerStateChanged) { // 手势进行
            // 告知collectionView更新交互移动
            [self.collectionView updateInteractiveMovementTargetPosition:curentPoint];
        } else if (pan.state == UIGestureRecognizerStateCancelled) { // 手势取消
            // 告知collectionView交互移动结束
            [self.collectionView cancelInteractiveMovement];
        } else if (pan.state == UIGestureRecognizerStateEnded) { // 手势结束
            // 告知collectionView交互移动结束
            [self.collectionView endInteractiveMovement];
        }
    }
    
    

    第三步:实现手势驱动必须的代理和数据源方法

    /**
     *  数据源方法,告知特定item是否可以移动
     */
    - (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath {
        return YES;
    }
    
    /**
     *  手势移动结束调动此方法,以最终确定目的地的IndexPath
     */
    - (NSIndexPath *)collectionView:(UICollectionView *)collectionView targetIndexPathForMoveFromItemAtIndexPath:(NSIndexPath *)originalIndexPath toProposedIndexPath:(NSIndexPath *)proposedIndexPath {
        // 通常直接返回proposedIndexPath即可
        return proposedIndexPath;
    }
    
    /**
     *  手势移动结束,系统调用此方法告知移动的对象开始的IndexPath、移动结束的IndexPath
     */
    - (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {
        // 在此处改变数据源中对应indexPath
    }
    

    本文所使用的Demo一上传至github

    相关文章

      网友评论

        本文标题:UICollectionView & UICollect

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