UICollectionViewCell 长按重排


首先,先祝博友们新年快乐!鸡年大吉! 在写这篇博文的时候,已经是1月25日,最后一天上班了,闲来无事,写下之前封装的拖动效果,希望能帮助有需要的博友们~


先上效果图。


1 、 全部 Cell 参与拖动效果。







2 、 禁止部分的 Cell 参与拖动。 (第一行的3个 cell 禁止)


  




3 、 跨 Cell 拖动。  (将一个 Cell 中的 UICollectionViewCell 拖动到另一个 Cell)








代码及类的简单说明。

1、 第一种拖动重排的方式

将Demo中的 UICollectionView+TLCellRearrange.h 拖到项目中,这是对 UICollectionView 做的一个扩展功能。



接口文件展示

#import <UIKit/UIKit.h>

@protocol UICollectionViewCellRearrangeDelegate <NSObject>

@optional

- (BOOL)collectionView:(nonnull UICollectionView *)collectionView
        shouldMoveCell:(nonnull UICollectionViewCell *)cell
           atIndexPath:(nonnull NSIndexPath *)indexPath;

- (void)collectionView:(nonnull UICollectionView *)collectionView
           putDownCell:(nonnull UICollectionViewCell *)cell
           atIndexPath:(nonnull NSIndexPath *)indexPath;

- (BOOL)collectionView:(nonnull UICollectionView *)collectionView
        shouldMoveCell:(nonnull UICollectionViewCell *)cell
         fromIndexPath:(nonnull NSIndexPath *)fromIndexPath
           toIndexPath:(nonnull NSIndexPath *)toIndexPath;

- (void)collectionView:(nonnull UICollectionView *)collectionView
           didMoveCell:(nonnull UICollectionViewCell *)cell
         fromIndexPath:(nonnull NSIndexPath *)fromIndexPath
           toIndexPath:(nonnull NSIndexPath *)toIndexPath;

@end

@interface UICollectionView (TLCellRearrange)

@property (nonatomic, weak) id<UICollectionViewCellRearrangeDelegate> _Nullable rearrangeDelegate;

@property (nonatomic, assign) BOOL enableRearrangement;

/**
 *  长按cell接触边缘时collectionView自动滚动的速率,每1/60秒移动的距离
 */
@property (nonatomic, assign) CGFloat autoScrollSpeed;

/**
 *  长按放大倍数
 *  默认为1.2
 */
@property (nonatomic, assign) CGFloat longPressMagnificationFactor;

@end



然后在使用的 VC 中, 创建  UICollectionView ,初始化之后,如下设置,加入扩展的委托 UICollectionViewCellRearrangeDelegate


- (void)setupUI {
    
    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
    [layout setScrollDirection:UICollectionViewScrollDirectionVertical];
    [_collectionView setCollectionViewLayout:layout];
    _collectionView.backgroundColor = [UIColor clearColor];
    _collectionView.showsVerticalScrollIndicator = NO;
    _collectionView.alwaysBounceVertical = YES;
    [_collectionView registerClass:[WBAreaCollectionViewCell class] forCellWithReuseIdentifier:[WBAreaCollectionViewCell reuseIdentifier]];
    _collectionView.rearrangeDelegate = self;
    _collectionView.enableRearrangement = YES;
    _collectionView.autoScrollSpeed = 3;
    _collectionView.longPressMagnificationFactor = 1.2;

    [self updateContent];
}


这样就能实现拖动的重排效果了。 然后拖动后的回调在对应的委托方法里 


#pragma mark - UICollectionViewCellRearrangeDelegate methods

- (BOOL)collectionView:(UICollectionView *)collectionView shouldMoveCell:(UICollectionViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    
    if (indexPath.row == 0 || indexPath.row == 1 || indexPath.row == 2) {
        // 如果不需要参与 长按拖动 和 重排效果 的cell, 在这里判断返回NO 即可。
        return NO;
    }
    NSLog(@"开始位置:%ld", indexPath.row);
    return YES;
}

- (void)collectionView:(UICollectionView *)collectionView putDownCell:(UICollectionViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    // 最终停留在的
    NSLog(@"最终到:%ld",  indexPath.row);
}

- (BOOL)collectionView:(UICollectionView *)collectionView shouldMoveCell:(UICollectionViewCell *)cell fromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
    
    id tempObject = self.areaModels[fromIndexPath.row];
    [self.areaModels removeObjectAtIndex:fromIndexPath.row];
    [self.areaModels insertObject:tempObject atIndex:toIndexPath.row];
    return YES;
}

- (void)collectionView:(UICollectionView *)collectionView didMoveCell:(UICollectionViewCell *)cell fromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
    // 途径的位置
    NSLog(@"从:%ld, 到x:%ld", fromIndexPath.row, toIndexPath.row);
}



2 、 禁止部分 Cell 参与重排的方式,只需要在对应的委托方法里设置返回 NO 即可, 如下 不想要 row = 0, 1, 2 的 Cell 参与重排。


- (BOOL)collectionView:(UICollectionView *)collectionView shouldMoveCell:(UICollectionViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    
    if (indexPath.row == 0 || indexPath.row == 1 || indexPath.row == 2) {
        // 如果不需要参与 长按拖动 和 重排效果 的cell, 在这里判断返回NO 即可。
        return NO;
    }
    NSLog(@"开始位置:%ld", indexPath.row);
    return YES;
}




3 、 跨 Cell 拖动重排。  这里主要讲下实现的思路,有了思路才能更好的自由扩展成自己的需要。


我们先看下最内部 设备 Cell 的接口文件。

#import <UIKit/UIKit.h>
@class WBDeviceModel;

@interface WBDeviceCollectionViewCell : UICollectionViewCell

+ (NSString *)reuseIdentifier;

- (void)setupWithModel:(WBDeviceModel *)deviceModel;

@property (nonatomic, assign) BOOL isDisableLongPress;  // 是否禁用自定义长按手势

@property (nonatomic, assign) BOOL isSelect;

@property (nonatomic, copy) void (^longPressDragBeganBlock)(CGPoint centerPoint);	// 长按开始

@property (nonatomic, copy) void (^longPressDragChangeBlock)(CGPoint centerPoint);	// 长按拖动过程

@property (nonatomic, copy) void (^longPressDragEndedBlock)(WBDeviceModel *currentDeviceModel, CGPoint centerPoint);	// 长按结束

@end

红色部分是我们自己提供出来的 长按手势 的过程接口,开始,过程和结束都是长按手势的一些基本阶段,我们可以按照自己的需要修改接口。

内部的实现,在长按的开始,将长按的 Cell 绘制成快照 self.snapshotView ,并且放大1.1倍 ,然后计算当前处于 Window 上的Frame,并且添加在 Window 上。
然后在拖动过程中,实时的计算基于 window 的位置,并且更新 Frame,并且实时回调最新的 Frame ( 如果你需要这个需求的话) 。
在拖动结束时,可以回调结束时的Frame ,以及你选中的数据。

因为这里的 UI 是有两层 Cell 之后才回调到 VC 的, WBDeviceCollectionViewCell 的数据是回调到了 WBAreaDetaliCollectionViewCell 中, WBAreaDetaliCollectionViewCell 再回调到 WBAreaDetailVC 。

#pragma mark - UIGestureRecognizer

- (void)selfContentViewLongPressed:(UILongPressGestureRecognizer *)sender {
    
    UIView *view = (UIView *)sender.view;
    
    UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
    
    if (sender.state == UIGestureRecognizerStateBegan) {
        
        _startPoint = [sender locationInView:sender.view];
        _originPoint = view.center;
        
        // iphone7及以上 或者 模拟器,自己绘制快照
        if ([[GetDeviceInfo() getCurrentDeviceModel] isEqualToString:@"iPhone7"] || [[GetDeviceInfo() getCurrentDeviceModel] isEqualToString:@"iPhone7Plus"] || [[GetDeviceInfo() getCurrentDeviceModel] isEqualToString:@"iPhoneSimulator"] ) {
                self.snapshotView = [[UIImageView alloc] initWithImage:[UIImage imageFromView:view]];
        } else {
            self.snapshotView = [view snapshotViewAfterScreenUpdates:NO];
        }
        
        // 添加在 window 上
        [window addSubview:self.snapshotView];
        CGRect toWindowFrame = [view convertRect:view.bounds toView:window];
        self.snapshotView.frame = toWindowFrame;
        
        [UIView animateWithDuration:Duration animations:^{
            // 放大1.1倍
            self.snapshotView.transform = CGAffineTransformMakeScale(1.1, 1.1);
            self.snapshotView.alpha = 0.9;
        }];
        
        BLOCK_SAFE_CALLS(self.longPressDragBeganBlock, [self fetchCenterPointWithRect:toWindowFrame]);
        
    } else if (sender.state == UIGestureRecognizerStateChanged) {
        
        CGPoint newPoint = [sender locationInView:sender.view];
        CGFloat deltaX = newPoint.x - _startPoint.x;
        CGFloat deltaY = newPoint.y - _startPoint.y;
        view.center = CGPointMake(view.center.x + deltaX, view.center.y + deltaY);
        
        CGRect toWindowFrame = [view convertRect:view.bounds toView:window];
        self.snapshotView.frame = toWindowFrame;
        
        BLOCK_SAFE_CALLS(self.longPressDragChangeBlock, [self fetchCenterPointWithRect:toWindowFrame]);
        
    } else if (sender.state == UIGestureRecognizerStateEnded) {
        
        [UIView animateWithDuration:Duration animations:^{
            self.snapshotView.transform = CGAffineTransformIdentity;
            self.snapshotView.alpha = 1.0;
        }];
        
        [self.snapshotView removeFromSuperview];
        self.snapshotView = nil;
        
        CGRect toWindowFrame = [view convertRect:view.bounds toView:window];
        
        BLOCK_SAFE_CALLS(self.longPressDragEndedBlock, self.currentDeviceModel, [self fetchCenterPointWithRect:toWindowFrame]);
    }
}



回调到 VC 之后,我们要做一种状态, 就是拖动到某个 Cell 上的响应状态, Demo 中是将背景色 更改为 浅白色 。
我们 Cell 返回的  centerPoint 自身中心点基于 window 的位置,然后就有下面这个方法, 利用这个返回的 centerPoint判断一个点是否在某一个区域内,即 是否在某个 Cell 上, 用中心点类判断,是为了确定这个Cell拖动的某个Cell上的唯一性,故不能用Cell的其他四点作为判断点。


// 选中项更新高亮
- (void)judgeDragPositionWithCenterPoint:(CGPoint)centerPoint indexPath:(NSIndexPath *)indexPath {
    
    NSArray<NSIndexPath *> *indexPaths = [self.collectionView indexPathsForVisibleItems];
    UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
    
    [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        
        if (indexPath.row != obj.row) {
            WBAreaDetaliCollectionViewCell *cell = (WBAreaDetaliCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:obj];
            CGRect toWindowFrame = [cell convertRect:cell.bounds toView:window];
            cell.isDragCurrent = CGRectContainsPoint(toWindowFrame, centerPoint) ? YES : NO;
        }
    }];
}



VC 上,Cell 的回调情况如下。


- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    
    WEAK_SELF();
    WBAreaDetaliCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:[WBAreaDetaliCollectionViewCell reuseIdentifier] forIndexPath:indexPath];
    
    // 初始化重用处理 ***
    [cell setupWithModel:self.areasDetail[indexPath.row]];
    // 内部collectionView的offset
    [cell setupCollectionViewOffsetWitnPointValue:self.pointsValue[INT_TO_STRING(indexPath.row)]];
    // ***
    
    // 长按拖动效果 ***
    cell.isDragCurrent = NO;
    [cell setPointChangeBlock:^(NSValue *pointValue) {
        [weakSelf.pointsValue setObject:pointValue forKey:INT_TO_STRING(indexPath.row)];
    }];
    
    [cell setLongPressDragBeganBlock:^(CGPoint centerPoint) {
        [weakSelf judgeDragPositionWithCenterPoint:centerPoint indexPath:indexPath];
    }];
    [cell setLongPressDragChangeBlock:^(CGPoint centerPoint) {
        [weakSelf judgeDragPositionWithCenterPoint:centerPoint indexPath:indexPath];
    }];
    [cell setLongPressDragEndedBlock:^(WBAreaDetailModel *currentAreaDetailModel, WBDeviceModel *currentDeviceModel, CGPoint centerPoint) {
        
        // 先判断一下拖动到的位置是在哪个 IndexPath 上 (可能为空)
        NSIndexPath *selectedIndexPath = [weakSelf returnSelectedItemWithCenterPoint:centerPoint indexPath:indexPath];
        [weakSelf.collectionView reloadItemsAtIndexPaths:@[ indexPath ]];
        if (!selectedIndexPath) {
            return ;
        } else {
            /*
             *  selectedAreaDetailModel     将要移动到的位置
             *  currentAreaDetailModel      当前数据源的位置
             *  currentDeviceModel          当前设备数据 Model
             */
            
            // 调用自己的重排 API  
            WBAreaDetailModel *selectedAreaDetailModel = self.areasDetail[selectedIndexPath.row];
            NSString *str = [NSString stringWithFormat:@"%@ - %@\n\n移动到\n\n%@", currentAreaDetailModel.roomName, currentDeviceModel.deviceName, selectedAreaDetailModel.roomName];
            ShowTips(str);
        }
    }];
    
    return cell;
}











  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值