UIcollectionView的 “拖入—添加” 操作

在UIcollectionView中實(shí)現(xiàn)類似電腦資源管理器里的那種將文件拖入圖標(biāo)就可以完成添加操作的效果,如圖:

QQ20181031-164939.gif

這個(gè)gif經(jīng)過壓縮,效果不太好。實(shí)際效果比圖上順滑很多。

首先我下載了 https://github.com/wazrx/XWDragCellCollectionView
這個(gè)控件作為基礎(chǔ),感謝作者!

XWDragCellCollectionView可以實(shí)現(xiàn)垂直/水平的滾動以及滑動排序,這個(gè)不是研究的重點(diǎn),我就不重復(fù)造輪子,而是在它的基礎(chǔ)上來改出我們想要的功能。

首先添加手勢,用長按手勢激活cell,來進(jìn)行接下來的操作:

- (void)initUI {
    UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
    
    UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT) collectionViewLayout:flowLayout];
    self.collectionView = collectionView;
    [self.view addSubview:collectionView];
    
    collectionView.delegate = self;
    collectionView.dataSource = self;
    [collectionView registerClass:[CollectionViewCell class] forCellWithReuseIdentifier:identity];
    collectionView.alwaysBounceVertical = YES;
    
    UILongPressGestureRecognizer *longGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongGesture:)];
    [collectionView addGestureRecognizer:longGesture];
}

這是長按手勢所要激活的方法:

- (void)handleLongGesture:(UILongPressGestureRecognizer *)longGesture {
    switch (longGesture.state) {
        case UIGestureRecognizerStateBegan:
            [self gestureBegan:longGesture];
            break;
        case UIGestureRecognizerStateChanged:
            [self gestureChange:longGesture];
            break;
        case UIGestureRecognizerStateEnded:
        case UIGestureRecognizerStateCancelled:
            [self gestureEndOrCancle:longGesture];
            break;
        default:

            break;
    }
}

方法中分別對操作的開始、拖動和結(jié)束/取消做了處理,先看開始的方法:

- (void)gestureBegan:(UILongPressGestureRecognizer *)longPressGesture {
    
    self.originalIndexPath = [self.collectionView indexPathForItemAtPoint:[longPressGesture locationOfTouch:0 inView:longPressGesture.view]];
    UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:self.originalIndexPath];//拿到被長按的格子
    
    //下面這一片是對這個(gè)格子截圖
    UIImage *snap;
    UIGraphicsBeginImageContextWithOptions(cell.bounds.size, 1.0f, 0);
    [cell.layer renderInContext:UIGraphicsGetCurrentContext()];
    snap = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    //把截好的圖片裝進(jìn)一個(gè)假的View里,之后用這個(gè)View隨手指拖動而運(yùn)動而隱藏原格子,給用戶造成其實(shí)是格子被拖動的效果
    UIView *tempMoveCell = [UIView new];
    tempMoveCell.layer.contents = (__bridge id)snap.CGImage;
    cell.hidden = YES;
    
    self.orignalCell = cell;
    self.orignalCenter = cell.center;
    
    self.tempMoveCell = tempMoveCell;
    self.tempMoveCell.frame = cell.frame;
    [self.collectionView addSubview:self.tempMoveCell];
    //開啟邊緣滾動定時(shí)器
    [self setEdgeTimer];
    //開啟抖動,和原控件不同,我這個(gè)是只抖兩下
    [self itemshake];
    self.lastPoint = [longPressGesture locationOfTouch:0 inView:longPressGesture.view];
}

- (void)itemshake {
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    [animation setDuration:0.1];
    animation.fromValue = @(-M_1_PI/6);
    animation.toValue = @(M_1_PI/6);
    animation.repeatCount = 1;
    animation.autoreverses = YES;
    self.tempMoveCell.layer.anchorPoint = CGPointMake(0.5, 0.5);
    [self.tempMoveCell.layer addAnimation:animation forKey:@"rotation"];
}

接下來是重點(diǎn)的拖動時(shí)候調(diào)用的方法:

- (void)gestureChange:(UILongPressGestureRecognizer *)longPressGesture {
    CGFloat tranX = [longPressGesture locationOfTouch:0 inView:longPressGesture.view].x - self.lastPoint.x;
    CGFloat tranY = [longPressGesture locationOfTouch:0 inView:longPressGesture.view].y - self.lastPoint.y;
    self.tempMoveCell.center = CGPointApplyAffineTransform(self.tempMoveCell.center, CGAffineTransformMakeTranslation(tranX, tranY));
    //讓你的假格子View隨手指移動
    self.lastPoint = [longPressGesture locationOfTouch:0 inView:longPressGesture.view];
    [self handleCell];
}

handleCell方法則負(fù)責(zé)分辨具體的操作

#define MoveSpace 20    //在格子里的這么大范圍內(nèi)也算move操作的觸發(fā)點(diǎn),而不是add操作的觸發(fā)點(diǎn)
- (void)handleCell {
    for (UICollectionViewCell *cell in [self.collectionView visibleCells]) {//遍歷所有的可視cell
        if ([self.collectionView indexPathForCell:cell] == _originalIndexPath) {//如果是自己這個(gè)cell,那么跳過
            continue;
        }
        //計(jì)算所有表格中心和在移動的格子中心的距離
        CGFloat spacingX = fabs(self.tempMoveCell.center.x - cell.center.x);
        CGFloat spacingY = fabs(_tempMoveCell.center.y - cell.center.y);
        
        if (self.motherCell == cell) {
            if (spacingX > _tempMoveCell.bounds.size.width / 2.0f - MoveSpace || spacingY > _tempMoveCell.bounds.size.height / 2.0f - MoveSpace) {
                //跑出了格子
                [self stopAddToCellWithData:NO];
            }
        }
        if (spacingX <= _tempMoveCell.bounds.size.width / 2.0f - MoveSpace && spacingY <= _tempMoveCell.bounds.size.height / 2.0f - MoveSpace) {
//            NSLog(@"進(jìn)入格子內(nèi)");
            self.motherCell = cell;
            [self setAddTimer];
            self.moveIndexPath = [self.collectionView indexPathForCell:cell];
        } else if (spacingX >= _tempMoveCell.bounds.size.width / 2.0f  - MoveSpace && spacingX <= _tempMoveCell.bounds.size.width / 2.0f + 7.5  && spacingY <= _tempMoveCell.bounds.size.height / 2.0f - MoveSpace) {
            //移動
            self.willMoveCell = cell;
            [self setMoveTimer];
            break;
        }
    }
}

看代碼到這里,你可能會發(fā)現(xiàn)setMoveTimer、setAddTimer 和 setEdgeTimer三個(gè)方法我并沒有解釋,這三個(gè)是計(jì)時(shí)器,用來延遲用戶操作,提升手感。比如setMoveTimer

- (void)setMoveTimer {
    if (!_moveTimer) {
        [self stopAddTimer];
        //        NSLog(@"新建timer");
        _moveTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(moveCell) userInfo:nil repeats:NO];
    }
}

- (void)stopMoveTimer {
    if (_moveTimer) {
        //        NSLog(@"銷毀timer");
        [_moveTimer invalidate];
        _moveTimer = nil;
    }
}

他規(guī)定了用戶將格子移動到會觸發(fā)move(排序)操作的位置時(shí)有0.5秒鐘的緩沖而setAddTimer:

- (void)setAddTimer {
    if (!_addTimer) {
        [self stopMoveTimer];
        //        NSLog(@"新建timer");
        _addTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(addToCell) userInfo:nil repeats:NO];
    }
}

- (void)stopAddTimer {
    if (_addTimer) {
        //        NSLog(@"銷毀timer");
        [_addTimer invalidate];
        _addTimer = nil;
    }
}

則讓add操作有1秒的緩沖,同時(shí)這兩個(gè)計(jì)時(shí)器在生成時(shí)都會ban掉對方,防止操作混亂。

setEdgeTimer是負(fù)責(zé)拖動按鈕移動到屏幕邊緣觸發(fā)滾動的。這個(gè)是原控件里就有的方法,不作講解。

在移動計(jì)時(shí)器到時(shí)間后,就會觸發(fā)move操作,此邏輯主要來自原控件

- (void)moveCell {
    NSLog(@"%@", [self.collectionView cellForItemAtIndexPath:self.originalIndexPath]);
    self.moveIndexPath = [self.collectionView indexPathForCell:self.willMoveCell];
    self.orignalCell = self.willMoveCell;
    self.orignalCenter = self.willMoveCell.center;
    [CATransaction begin];
    [self.collectionView moveItemAtIndexPath:self.originalIndexPath toIndexPath:self.moveIndexPath];
    [CATransaction setCompletionBlock:^{
        //                NSLog(@"動畫完成");
        [self stopMoveTimer];
        
    }];
    [CATransaction commit];
    self.originalIndexPath = self.moveIndexPath;
}

而add操作則分兩步:開始add和結(jié)束add。結(jié)束add又分兩種情況:完成add操作和取消add操作。完成add操作要對文件進(jìn)行更改,取消add操作要將表格還原到操作之前的狀態(tài)。首先看開始add:

- (void)addToCell {
//    NSLog(@"開始add操作");
    if (self.motherCell) {
//        NSLog(@"開始放大");
        //跟上面一樣,對添加操作作為文件夾一方的格子(motherCell)進(jìn)行截圖
        UIImage *snap;
        UIGraphicsBeginImageContextWithOptions(self.motherCell.bounds.size, 1.0f, 0);
        [self.motherCell.layer renderInContext:UIGraphicsGetCurrentContext()];
        snap = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        //把截圖裝進(jìn)一個(gè)假的View里
        UIView *bigMotherCell = [UIView new];
        bigMotherCell.layer.contents = (__bridge id)snap.CGImage;
//        self.motherCell.hidden = YES;
        
        self.bigMotherCell = bigMotherCell;
        self.bigMotherCell.frame = self.motherCell.frame;
        [self.collectionView addSubview:self.bigMotherCell];
        CGRect rect = self.bigMotherCell.frame;
        CGFloat scale = 1.3;
        [self.collectionView bringSubviewToFront:self.tempMoveCell];
        
        //方法這個(gè)假View,造成文件夾要容納文件的效果
        [UIView animateWithDuration:0.5 animations:^{
            self.bigMotherCell.frame = CGRectMake(rect.origin.x - rect.size.width * (scale - 1)/2, rect.origin.y - rect.size.height * (scale - 1)/2, rect.size.width * scale, rect.size.height * scale);
        } completion:nil];
    } else {
//        NSLog(@"沒有motherCell");
    }
}

結(jié)束add的兩種情況我選擇用一個(gè)bool值進(jìn)行區(qū)分,yes是完成操作,no是取消操作

- (void)stopAddToCellWithData:(BOOL)withData {
    if (self.motherCell) {
        [UIView animateWithDuration:0.1 animations:^{
            self.bigMotherCell.frame = self.motherCell.frame;
            if (withData) {
                //        NSIndexPath *motherIndexPath = [self.collectionView indexPathForCell:self.motherCell];
                        NSIndexPath *childIndexPath = [self.collectionView indexPathForCell:self.orignalCell];
                NSLog(@"把編號為%@的格子移動到編號為%@的格子里",((CollectionViewCell *)self.orignalCell).number, ((CollectionViewCell *)self.motherCell).number);
                [self.dataArray removeObjectAtIndex:childIndexPath.row];
                [self.collectionView deleteItemsAtIndexPaths:@[childIndexPath]];
            }
        } completion:^(BOOL finished) {
            [self.bigMotherCell removeFromSuperview];
            self.motherCell = nil;
            [self stopAddTimer];
        }];
    }
}

最后就是結(jié)束長按的方法,他主要負(fù)責(zé)讓一切回歸初始

- (void)gestureEndOrCancle:(UILongPressGestureRecognizer *)longPressGesture {
    self.collectionView.userInteractionEnabled = NO;
    [self stopEdgeTimer];
    if (self.motherCell) {
        [self stopAddToCellWithData:YES];
        [self removeTempMoveCell];
    } else {
        [UIView animateWithDuration:0.25 animations:^{
            self.tempMoveCell.center = self.orignalCenter;
        } completion:^(BOOL finished) {
            [self removeTempMoveCell];
        }];
    }
}

這就是拖入-添加操作的所有邏輯,如果還不明白可以來這里下載Demo試試:
https://github.com/zyf25333/drop-addCollectionView

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容