UIcollectionView的 「拖入—添加」 操做

在UIcollectionView中實現相似電腦資源管理器裏的那種將文件拖入圖標就能夠完成添加操做的效果,如圖:git

圖片描述

這個gif通過壓縮,效果不太好。實際效果比圖上順滑不少。github

首先我下載了 https://github.com/wazrx/XWDr...
這個控件做爲基礎,感謝做者!ide

XWDragCellCollectionView能夠實現垂直/水平的滾動以及滑動排序,這個不是研究的重點,我就不重複造輪子,而是在它的基礎上來改出咱們想要的功能。動畫

首先添加手勢,用長按手勢激活cell,來進行接下來的操做:spa

- (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];
}

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

- (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;
    }
}

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

- (void)gestureBegan:(UILongPressGestureRecognizer *)longPressGesture {
    
    self.originalIndexPath = [self.collectionView indexPathForItemAtPoint:[longPressGesture locationOfTouch:0 inView:longPressGesture.view]];
    UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:self.originalIndexPath];//拿到被長按的格子
    
    //下面這一片是對這個格子截圖
    UIImage *snap;
    UIGraphicsBeginImageContextWithOptions(cell.bounds.size, 1.0f, 0);
    [cell.layer renderInContext:UIGraphicsGetCurrentContext()];
    snap = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    //把截好的圖片裝進一個假的View裏,以後用這個View隨手指拖動而運動而隱藏原格子,給用戶形成實際上是格子被拖動的效果
    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];
    //開啓邊緣滾動定時器
    [self setEdgeTimer];
    //開啓抖動,和原控件不一樣,我這個是隻抖兩下
    [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"];
}

接下來是重點的拖動時候調用的方法:blog

- (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方法則負責分辨具體的操做排序

#define MoveSpace 20    //在格子裏的這麼大範圍內也算move操做的觸發點,而不是add操做的觸發點
- (void)handleCell {
    for (UICollectionViewCell *cell in [self.collectionView visibleCells]) {//遍歷全部的可視cell
        if ([self.collectionView indexPathForCell:cell] == _originalIndexPath) {//若是是本身這個cell,那麼跳過
            continue;
        }
        //計算全部表格中心和在移動的格子中心的距離
        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(@"進入格子內");
            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;
        }
    }
}

看代碼到這裏,你可能會發現setMoveTimer、setAddTimer 和 setEdgeTimer三個方法我並無解釋,這三個是計時器,用來延遲用戶操做,提高手感。好比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;
    }
}

他規定了用戶將格子移動到會觸發move(排序)操做的位置時有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秒的緩衝,同時這兩個計時器在生成時都會ban掉對方,防止操做混亂。

setEdgeTimer是負責拖動按鈕移動到屏幕邊緣觸發滾動的。這個是原控件裏就有的方法,不做講解。

在移動計時器到時間後,就會觸發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和結束add。結束add又分兩種狀況:完成add操做和取消add操做。完成add操做要對文件進行更改,取消add操做要將表格還原到操做以前的狀態。首先看開始add:

- (void)addToCell {
//    NSLog(@"開始add操做");
    if (self.motherCell) {
//        NSLog(@"開始放大");
        //跟上面同樣,對添加操做做爲文件夾一方的格子(motherCell)進行截圖
        UIImage *snap;
        UIGraphicsBeginImageContextWithOptions(self.motherCell.bounds.size, 1.0f, 0);
        [self.motherCell.layer renderInContext:UIGraphicsGetCurrentContext()];
        snap = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        //把截圖裝進一個假的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];
        
        //方法這個假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");
    }
}

結束add的兩種狀況我選擇用一個bool值進行區分,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];
        }];
    }
}

最後就是結束長按的方法,他主要負責讓一切迴歸初始

- (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/d...

相關文章
相關標籤/搜索