39

iOS collectionView拖拽排序

 5 years ago
source link: http://www.cocoachina.com/ios/20190130/26271.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

项目中首页按钮按照需求需要实现拖拽排序并且记录排序后的布局,下次再进入APP后展示排序后的布局。

功能分析

实现此功能需要实现两个点,第一就是拖拽排序的实现,第二就是存储排序后的布局,针对第一个功能点,拖拽排序:这个可以使用collectionView系统自带的功能来实现,针对第二个功能点,我使用NSUserDefaults本地存储盛放collectionViewCell内容的数组。

整体效果如图:

bMBVbin.png!web

本地存储的实现

每个cell由背景图片和title组成,NSDictionary *dic1 = @{@"title":@"标题", @"img":@"bgImg"};每个字典存放两个字段,根据需求,创建相应的字典,NSArray *tempArr = @[dic1,dic2,dic3,dic4,dic5]``[self.homeBtnsArr addObjectsFromArray:tempArr];将字典放在数组中,便于在collectionView的代理方法中使用,[[NSUserDefaults standardUserDefaults] setObject:self.homeBtnsArr forKey:@"stuHomeBtns"];将数组做本地储存,在每次进入界面时,先去NSUserDefaults中查找存放的数组,再去使用。

拖拽移动功能的实现

1、创建collectionView

UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
    flowLayout.itemSize = CGSizeMake((SCREEN_WIDTH-50.0)/4, 85);
    flowLayout.minimumLineSpacing = 0.1;
    flowLayout.minimumInteritemSpacing = 0.1;
    UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:flowLayout];
    collectionView.backgroundColor = [UIColor whiteColor];
    collectionView.delegate = self;
    collectionView.dataSource = self;
    self.collectionView = collectionView;
    [self addSubview:collectionView];
    [collectionView makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(0);
    }];
    [collectionView registerClass:[HomeSortBtnCell class] forCellWithReuseIdentifier:@"HomeSortBtnCell"];

2、在collectionView上添加长按手势

UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handlelongGesture:)];
    [collectionView addGestureRecognizer:longPress];

3、长按手势响应方法

#pragma mark 长按响应方法
- (void)handlelongGesture:(UILongPressGestureRecognizer *)longPress {
    [self action:longPress];
}

4、处理长按手势

- (void)action:longPress:(UILongPressGestureRecognizer *)longPress {
    switch (longPress.state) {
        case UIGestureRecognizerStateBegan:
            {
                //手势开始
                //判断手势落点位置是否在row上
                NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:[longPress locationInView:self.collectionView]];
                if (indexPath == nil) {
                    break;
                }
                HomeSortBtnCell *cell = (HomeSortBtnCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
                [self bringSubviewToFront:cell];
                //iOS9 方法 移动cell
                [self.collectionView beginInteractiveMovementForItemAtIndexPath:indexPath];
            }
            break;
        case UIGestureRecognizerStateChanged:
            {
                //iOS9 方法 移动过程中随时更新cell位置
                [self.collectionView updateInteractiveMovementTargetPosition:[longPress locationInView:self.collectionView]];
            }
            break;
        case UIGestureRecognizerStateEnded:
            {
                //手势结束
                //iOS9方法 移动结束后关闭cell移动
                [self.collectionView endInteractiveMovement];
            }
            break;
        default:
            [self.collectionView cancelInteractiveMovement];
            break;
    }
}

这里处理长按手势的时候,(一)、判断手势开始,判断手势落点位置是否在collectionView的cell上,collectionView系统的方法:indexPathForItemAtPoint:这里的point为:[longPress locationInView:self.collectionView],获取到手势落点的indexPath后,根据indexPath查找到相应位置的cell,然后[self bringSubviewToFront:cell];将cell提到前层展示,给人以悬浮的感觉,这时就开始了collectionView的移动操作:[self.collectionView beginInteractiveMovementForItemAtIndexPath:indexPath];。(二)、当手势在移动的过程中,collectionView随时更新cell的位置:[self.collectionView updateInteractiveMovementTargetPosition:[longPress locationInView:self.collectionView]];。(三)、手势结束后,collectionView也随之关闭cell的移动:[self.collectionView endInteractiveMovement];。

这里不仅仅需要处理手势事件,还需要遵循collectionView相关的代理方法:

//开启collectionView可以移动
- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath {
    return YES;
}
//处理collectionView移动过程中的数据操作
- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {
    //取出移动row 数据
    NSDictionary *dic = self.viewModel.homeBtnsArr[sourceIndexPath.row];
    //从数据源中移除该数据
    [self.viewModel.homeBtnsArr removeObject:dic];
    
    //将数据插入到数据源中目标位置
    [self.viewModel.homeBtnsArr insertObject:dic atIndex:destinationIndexPath.row];
    
    NSArray *tempBtns = [self.viewModel.homeBtnsArr copy];
    if ([self.viewModel fetchIsTeacherRole]) {
        //教师端
        [[NSUserDefaults standardUserDefaults] setObject:tempBtns forKey:@"teaHomeBtns"];
    } else {
        [[NSUserDefaults standardUserDefaults] setObject:tempBtns forKey:@"stuHomeBtns"];
    }
}

在- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath代理方法中,(一)、 //取出移动row 数据

NSDictionary dic = self.viewModel.homeBtnsArr[sourceIndexPath.row];(二)、//从数据源中移除该数据 [self.viewModel.homeBtnsArr removeObject:dic];(三)、//将数据插入到数据源中目标位置 [self.viewModel.homeBtnsArr insertObject:dic atIndex:destinationIndexPath.row];(四)、NSArray *tempBtns = [self.viewModel.homeBtnsArr copy];[[NSUserDefaults standardUserDefaults] setObject:tempBtns forKey:@"teaHomeBtns"];将改变后的cell内容数组存储到本地。

以上就是iOS9及以上移动collectionViewCell的方法,我们利用系统封装好的方法,相对比较容易的实现了功能。接下来看一下iOS9以下我们需要怎么实现

iOS9以下

#pragma mark iOS9之前的方法
- (void)action:(UILongPressGestureRecognizer *)longPress {
    switch (longPress.state) {
        case UIGestureRecognizerStateBegan:
            {
                //手势开始 判断手势落点位置是否在row上
                NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:[longPress locationInView:self.collectionView]];
                self.oldIndexPath = indexPath;
                if (indexPath == nil) {
                    break;
                }
                HomeSortBtnCell *cell = (HomeSortBtnCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
                //使用系统的截图功能 得到cell的截图视图
                UIView *snapshotView = [cell snapshotViewAfterScreenUpdates:NO];
                snapshotView.frame = cell.frame;
                [self addSubview:self.snapShotView = snapshotView];
                //截图后隐藏当前cell
                cell.hidden = YES;
                
                CGPoint currentPoint = [longPress locationInView:self.collectionView];
                [UIView animateWithDuration:0.25 animations:^{
                    snapshotView.transform = CGAffineTransformMakeScale(1.05, 1.05);
                    snapshotView.center = currentPoint;
                }];
                
                //取出移动row 数据
//                NSDictionary *dic = self.viewModel.homeBtnsArr[self.oldIndexPath.row];
//                //从数据源中移除该数据
//                [self.viewModel.homeBtnsArr removeObject:dic];
                
            }
            break;
        case UIGestureRecognizerStateChanged:
            {
                //手势改变 当前手指位置 截图视图位置随着手指移动而移动
                CGPoint currentPoint = [longPress locationInView:self.collectionView];
                self.snapShotView.center = currentPoint;
                //计算截图视图和哪个可见cell相交
                for (HomeSortBtnCell *cell in self.collectionView.visibleCells) {
                    //当前隐藏的cell就不需要交换了 直接continue
                    if ([self.collectionView indexPathForCell:cell] == self.oldIndexPath) {
                        continue;
                    }
                    //计算中心距
                    CGFloat space = sqrt(pow(self.snapShotView.center.x-cell.center.x, 2) + powf(self.snapShotView.center.y - cell.center.y, 2));
                    //如果相交一半就移动
                    if (space <= self.snapShotView.bounds.size.width/2) {
                        self.moveIndexPath = [self.collectionView indexPathForCell:cell];
                       /*更新数据源 须在移动之前*/
                        //取出移动row 数据
                        NSDictionary *dic = self.viewModel.homeBtnsArr[self.oldIndexPath.row];
                        //从数据源中移除该数据
                        [self.viewModel.homeBtnsArr removeObject:dic];
                        //将数据插入到数据源中目标位置
                        [self.viewModel.homeBtnsArr insertObject:dic atIndex:self.moveIndexPath.row];
                        
                        //移动 会调用MoveToIndexPath方法更新数据源
                        [self.collectionView moveItemAtIndexPath:self.oldIndexPath toIndexPath:self.moveIndexPath];
                        
                        //设置移动后的起始indexPath
                        self.oldIndexPath = self.moveIndexPath;
                        break;
                    }
                }
            }
            break;
        default:
            {
             //手势结束和其他状态
                HomeSortBtnCell *cell = (HomeSortBtnCell *)[self.collectionView cellForItemAtIndexPath:self.oldIndexPath];
                //结束动画过程中停止交互,防止出问题
                self.collectionView.userInteractionEnabled = NO;
                //给截图视图一个动画移动到隐藏cell的新位置
                [UIView animateWithDuration:0.25 animations:^{
                    self.snapShotView.center = cell.center;
                    self.snapShotView.transform = CGAffineTransformMakeScale(1.0, 1.0);
                } completion:^(BOOL finished) {
                    //移除截图视图,显示隐藏的cell并开始交互
                    [self.snapShotView removeFromSuperview];
                    cell.hidden = NO;
                    self.collectionView.userInteractionEnabled = YES;
                    
                    //本地存储已修改的按钮数组
                    NSArray *tempBtns = [self.viewModel.homeBtnsArr copy];
                    if ([self.viewModel fetchIsTeacherRole]) {
                        //教师端
                        [[NSUserDefaults standardUserDefaults] setObject:tempBtns forKey:@"teaHomeBtns"];
                    } else {
                        [[NSUserDefaults standardUserDefaults] setObject:tempBtns forKey:@"stuHomeBtns"];
                    }
                }];
            }
            break;
    }
}

iOS9以下就相对 麻烦些了,首先在手势开始时,我们也同样需要找到手势落点所在位置的cell,然后使用系统的截图功能,得到cell的截图视图

UIView *snapshotView = [cell snapshotViewAfterScreenUpdates:NO]; snapshotView.frame = cell.frame; [self addSubview:self.snapShotView = snapshotView]; //截图后隐藏当前cell cell.hidden = YES; CGPoint currentPoint = [longPress locationInView:self.collectionView]; [UIView animateWithDuration:0.25 animations:^{ snapshotView.transform = CGAffineTransformMakeScale(1.05, 1.05); snapshotView.center = currentPoint; }];

这里我们做这么多的操作,在iOS9及以上我们相应做的操作是:[self bringSubviewToFront:cell]; //iOS9 方法 移动cell [self.collectionView beginInteractiveMovementForItemAtIndexPath:indexPath];。当手势发生改变,也就是我们开始移动的时候,截图的视图位置也随着手指移动而移动CGPoint currentPoint = [longPress locationInView:self.collectionView]; self.snapShotView.center = currentPoint;我们需要自己计算截图视图和哪个可见cell相交:for (HomeSortBtnCell *cell in self.collectionView.visibleCells) { //当前隐藏的cell就不需要交换了 直接continue if ([self.collectionView indexPathForCell:cell] == self.oldIndexPath) { continue; } //计算中心距 CGFloat space = sqrt(pow(self.snapShotView.center.x-cell.center.x, 2) + powf(self.snapShotView.center.y - cell.center.y, 2)); //如果相交一半就移动 if (space<= self.snapShotView.bounds.size.width/2) { self.moveIndexPath = [self.collectionView indexPathForCell:cell]; /*更新数据源 须在移动之前*/ //取出移动row 数据 NSDictionary *dic = self.viewModel.homeBtnsArr[self.oldIndexPath.row]; //从数据源中移除该数据 [self.viewModel.homeBtnsArr removeObject:dic]; //将数据插入到数据源中目标位置 [self.viewModel.homeBtnsArr insertObject:dic atIndex:self.moveIndexPath.row]; //移动 会调用MoveToIndexPath方法更新数据源 [self.collectionView moveItemAtIndexPath:self.oldIndexPath toIndexPath:self.moveIndexPath]; //设置移动后的起始indexPath self.oldIndexPath = self.moveIndexPath; break; } }

手势结束之后,我们需要将截图的视图隐藏移除,并且刷新当前视图布局:

//手势结束和其他状态 HomeSortBtnCell *cell = (HomeSortBtnCell *)[self.collectionView cellForItemAtIndexPath:self.oldIndexPath]; //结束动画过程中停止交互,防止出问题 self.collectionView.userInteractionEnabled = NO; //给截图视图一个动画移动到隐藏cell的新位置 [UIView animateWithDuration:0.25 animations:^{ self.snapShotView.center = cell.center; self.snapShotView.transform = CGAffineTransformMakeScale(1.0, 1.0); } completion:^(BOOL finished) { //移除截图视图,显示隐藏的cell并开始交互 [self.snapShotView removeFromSuperview]; cell.hidden = NO; self.collectionView.userInteractionEnabled = YES; //本地存储已修改的按钮数组 NSArray *tempBtns = [self.viewModel.homeBtnsArr copy]; if ([self.viewModel fetchIsTeacherRole]) { //教师端 [[NSUserDefaults standardUserDefaults] setObject:tempBtns forKey:@"teaHomeBtns"]; } else { [[NSUserDefaults standardUserDefaults] setObject:tempBtns forKey:@"stuHomeBtns"]; } }];

上述就是实现这个功能的基本流程了。ps iOS9以下的设备已经较少了,我们现在大多使用第一种方法了,相比较而言还是有优势的。

作者:劉光軍_Shine

链接:https://www.jianshu.com/p/e9eb12267109


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK