从零开始UICollectionView(2)--可移动的Cell及9.0以下的解决方案

前言

本文的所有代码,是基于上一篇UICollectionView(1)--基本实现的代码上编写的,目的是为了代码的连贯性。在文章系列的结束,会给出代码Demo。

上一节,我们基本实现了一个纵向的UICollectionView,但是所有的Cell都是定住无法移动的。当然我们也可以通过数据源的改动然后[self.mainCollectionView reloadData]来更新UI,甚至只更新局部Cell。

但是更体现交互性质的是,用户可以直接在UI上移动Cell,而你要做的是更改对应数据在数据源中的位置。
我们使用的是长按手势开启UICollectionView,通过长按手势在UICollectionView上的移动位置,来移动Cell,同时不断的更换选中的Cell在数据源中的位置。在长按手势结束或者被打断等状况下结束移动。原理说清楚了,下面直接上代码。





1.为UICollectionView添加长按手势。

-(UICollectionView *)mainCollectionView
{
    if (!_mainCollectionView) {
        UICollectionViewFlowLayout * layout = [UICollectionViewFlowLayout new];
        layout.scrollDirection = UICollectionViewScrollDirectionVertical;
        layout.sectionHeadersPinToVisibleBounds = NO;//头部视图悬停设为YES
        layout.sectionFootersPinToVisibleBounds = NO;//尾部视图悬停设为YES
    
        _mainCollectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
        _mainCollectionView.backgroundColor = [UIColor whiteColor];
        _mainCollectionView.delegate = self;
        _mainCollectionView.dataSource = self;
    
        [_mainCollectionView registerClass:[MainCollectionViewCell class] forCellWithReuseIdentifier:[MainCollectionViewCell reuseIdentifier]];
        [_mainCollectionView registerClass:[MainCollectionHeaderView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:[MainCollectionHeaderView reuseIdentifier]];
        [_mainCollectionView registerClass:[MainCollectionFooterView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:[MainCollectionFooterView reuseIdentifier]];
    
        //添加长按手势来移动 item
        UILongPressGestureRecognizer * longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(didReceiveLongPress:)];
        [_mainCollectionView addGestureRecognizer:longPress];
        _mainCollectionView.userInteractionEnabled = YES;
    }
    
    return _mainCollectionView;
}

2.在长按手势对应的方法中开启和移动Cell。
#pragma mark - private methods
//长按手势状态控制移动 item
-(void)didReceiveLongPress:(UILongPressGestureRecognizer *)longpress
{
if (self.mainCollectionView == nil) return;

    switch (longpress.state) {
        case UIGestureRecognizerStateBegan:
        {
            //搜寻长按手势在UICollectionView上的位置,若不为nil则开启移动对应位置Cell
            NSIndexPath * indexpath = [self.mainCollectionView indexPathForItemAtPoint:[longpress locationInView:self.mainCollectionView]];
            if (indexpath == nil) {
                break;
            }
            [self.mainCollectionView beginInteractiveMovementForItemAtIndexPath:indexpath];
        }
            break;
        case UIGestureRecognizerStateChanged:
        {
            //长按后移动的情况下,不断通过手势对应的位置更新被选中的Cell的位置
            [self.mainCollectionView updateInteractiveMovementTargetPosition:[longpress locationInView:self.mainCollectionView]];
        }
            break;
        case UIGestureRecognizerStateEnded:
        {
            //长按手势结束,关闭移动
            [self.mainCollectionView endInteractiveMovement];
        }
            break;
        default:
        {
            //其它情况,关闭移动
            [self.mainCollectionView endInteractiveMovement];
        }
            break;
    }
}

3.在UICollectionViewDataSource方法里面有一个- (BOOL)collectionView:canMoveItemAtIndexPath:NS_AVAILABLE_IOS(9_0);的方法,通过返回一个BOOL值来验证是否可以移动Cell。
//是否允许移动 item
- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(9_0)
{
return YES;
}

4.UI完成后也要更新对应数据在数据源中的位置,系统提供一个- (void)collectionView:moveItemAtIndexPath:toIndexPath: NS_AVAILABLE_IOS(9_0);的回调方法。

//移动 item 行为
- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath NS_AVAILABLE_IOS(9_0)
{
    NSMutableArray * moveArray = [NSMutableArray     arrayWithArray:self.dataArray];

    MainModel * model = moveArray[sourceIndexPath.section]    [sourceIndexPath.item];
    NSMutableArray * subArray = [NSMutableArray arrayWithArray:moveArray[sourceIndexPath.section]];
    [subArray removeObject:model];
    moveArray[sourceIndexPath.section] = [NSArray arrayWithArray:subArray];

    NSMutableArray * subToArray = [NSMutableArray arrayWithArray:moveArray[destinationIndexPath.section]];
    [subToArray insertObject:model atIndex:destinationIndexPath.item];
    moveArray[destinationIndexPath.section] = [NSArray arrayWithArray:subToArray];

    self.dataArray = [NSArray arrayWithArray:moveArray];
}

5.效果图。


从零开始UICollectionView(2)--可移动的Cell及9.0以下的解决方案_第1张图片
自由移动Cell

有朋友问这是iOS9.0之后的方法,那iOS9.0之前呢,解决方法如下:
1.原理:我们在长按的开始,获取长按的Cell,将其复制一份,并将原来的Cell隐藏,让复制的View跟着我们的移动而移动,并且在移动的过程中不断将真实的隐藏了的Cell跟我们移动所到位置的Cell做交换,千万记住,数据源中的交换必须发生在UI上的交换之前。
2.代码:

//长按手势状态控制移动 item
-(void)didReceiveLongPress:(UILongPressGestureRecognizer *)longpress
{
    if (self.mainCollectionView == nil) return;

    CGPoint location = [longpress locationInView:self.mainCollectionView];

    NSIndexPath * indexpath = [self.mainCollectionView indexPathForItemAtPoint:location];

    static UICollectionViewCell * cell;
    static UIView * fakeCell;
    static CGPoint beginLocation;
    static NSIndexPath * beginIndex;

    switch (longpress.state) {
        case UIGestureRecognizerStateBegan:
        {
            cell = [self.mainCollectionView cellForItemAtIndexPath:indexpath];
            cell.hidden = YES;
        
            fakeCell = [self customSnapshoFromView:cell.contentView];
            fakeCell.frame = cell.frame;
            [self.mainCollectionView addSubview:fakeCell];
        
            beginLocation = location;
            beginIndex = indexpath;
        }
            break;
        case UIGestureRecognizerStateChanged:
        {
            CGFloat moveX = location.x-beginLocation.x;
            CGFloat moveY = location.y-beginLocation.y;
        
            fakeCell.center = CGPointMake(fakeCell.center.x+moveX, fakeCell.center.y+moveY);
        
            beginLocation = location;
        
        
            NSIndexPath * nowIndexpath = [self.mainCollectionView indexPathForItemAtPoint:location];
            UIView * nowView = [self.mainCollectionView cellForItemAtIndexPath:nowIndexpath];

            if ((nowIndexpath.section==0&&nowIndexpath.row==0)&&(nowView==nil)) {
            
                nowIndexpath = [NSIndexPath indexPathForItem:[self.mainCollectionView numberOfItemsInSection:beginIndex.section]-1 inSection:beginIndex.section];

            }

            [self changeDataSourcesFromSources:beginIndex toDestination:nowIndexpath];
        
            [self.mainCollectionView moveItemAtIndexPath:beginIndex toIndexPath:nowIndexpath];
        
            beginIndex = nowIndexpath;
        }
            break;
        case UIGestureRecognizerStateEnded:
        {
            [fakeCell removeFromSuperview];
            fakeCell = nil;
        
            cell.hidden = NO;
        }
            break;
        default:
        {            
            [fakeCell removeFromSuperview];
            fakeCell = nil;
        
            cell.hidden = NO;

        }
            break;
    }
}

-(void)changeDataSourcesFromSources:(NSIndexPath *)sourceIndexPath toDestination:(NSIndexPath *)destinationIndexPath
{
    NSMutableArray * moveArray = [NSMutableArray arrayWithArray:self.dataArray];

    MainModel * model = moveArray[sourceIndexPath.section][sourceIndexPath.item];
    NSMutableArray * subArray = [NSMutableArray arrayWithArray:moveArray[sourceIndexPath.section]];
    [subArray removeObject:model];
    moveArray[sourceIndexPath.section] = [NSArray arrayWithArray:subArray];

    NSMutableArray * subToArray = [NSMutableArray arrayWithArray:moveArray[destinationIndexPath.section]];
    [subToArray insertObject:model atIndex:destinationIndexPath.item];
    moveArray[destinationIndexPath.section] = [NSArray arrayWithArray:subToArray];

    self.dataArray = [NSArray arrayWithArray:moveArray];
}

#pragma mark 创建cell的快照
- (UIView *)customSnapshoFromView:(UIView *)inputView {
    // 用cell的图层生成UIImage,方便一会显示
    UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, NO, 0);
    [inputView.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
// 自定义这个快照的样子(下面的一些参数可以自己随意设置)
    UIView *snapshot = [[UIImageView alloc] initWithImage:image];
//    snapshot.layer.masksToBounds = NO;
//    snapshot.layer.cornerRadius = 0.0;
//    snapshot.layer.shadowOffset = CGSizeMake(-5.0, 0.0);
//    snapshot.layer.shadowRadius = 5.0;
//    snapshot.layer.shadowOpacity = 0.4;
    return snapshot;
}

下节预告:纵向瀑布流展示。

瀑布流

注:若你觉得这文章确实帮到你了,或是支持一下原创技术文章,请为我点个赞。大爷若是还能打赏打赏,那就更好不过了。
从零开始UICollectionView(2)--可移动的Cell及9.0以下的解决方案_第2张图片

你可能感兴趣的:(从零开始UICollectionView(2)--可移动的Cell及9.0以下的解决方案)