UICollectionViewCell 长按重排

来源:互联网 发布:9月21网络大电影票房 编辑:程序博客网 时间:2024/05/29 18:01


首先,先祝博友们新年快乐!鸡年大吉! 在写这篇博文的时候,已经是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;}






CSDN下载





1 0
原创粉丝点击