iOS

UICollectionView & UICollect

2016-05-22  本文已影响138人  FlowerKanZhe
视觉

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

上一篇下一篇

猜你喜欢

热点阅读